尝试使用 condition_variables 在线程之间切换

Posted

技术标签:

【中文标题】尝试使用 condition_variables 在线程之间切换【英文标题】:Trying to switch between threads using condition_variables 【发布时间】:2017-10-26 21:07:03 【问题描述】:

我有两个线程。两者都被初始化,但第一个线程首先运行,而第二个线程等待第一个线程 (call_once(flagEven1, [&]()cond2.wait(lock, [&]()return zeroEvenOddFlag == 1; ); );)。在满足某个条件后,第一个线程唤醒第二个线程并进入睡眠状态。

                zeroEvenOddFlag = 0;
                cond1.notify_all();
                cond2.wait(lock, [&]()return zeroEvenOddFlag == 1; );

此过程交替进行,直到任务完成。现在线程刚刚被锁定,我不知道发生了什么。线程应该一起处理输入字符串并产生两个零和非零数字字符串。我通过添加线程之间共享的整数 (zeroEvenOddFlag) 来纠正虚假唤醒。

#include <string>
#include <thread>
#include <future>
#include <cstdio>
#include <iostream>
#include <queue>
#include <condition_variable>

using namespace std;

mutex mu;
condition_variable cond1;
condition_variable cond2;

once_flag flagZero;
once_flag flagEven;
once_flag flagEven1;
int zeroEvenOddFlag = 0;

string printerFunc(queue<char>& input, int zeroEvenOdd, once_flag& flag)
    string output = "";
    function<void(string&)> f;
    unique_lock<mutex> lock(mu);
    call_once(flag, [&]()
        if (zeroEvenOdd == 1)
            f = [&](string& output)
                int i = input.front() - '0';
                if (i == 0) 
                output += to_string(i); 
                input.pop();
                
                else 
                    zeroEvenOddFlag = 1;
                    cond2.notify_all();
                    cond1.wait(lock, [&]()return zeroEvenOddFlag == 0; );
                
            ;
        
        else if (zeroEvenOdd == 2)
            call_once(flagEven1, [&]()cond2.wait(lock, [&]()return zeroEvenOddFlag == 1; ); );
            f = [&](string& output)
                int i = input.front() - '0';
                if (i != 0) 
                    output += to_string(i); 
                    input.pop();
                
                else 
                    zeroEvenOddFlag = 0;
                    cond1.notify_all();
                    cond2.wait(lock, [&]()return zeroEvenOddFlag == 1; );
                
            ;
        
    );
    while(input.size()!=0)
        f(output);
    
    return output;


int _tmain(int argc, _TCHAR* argv[])

     string input = "0102030";
     queue<char> inputQueue;
     for(char c : input)
         inputQueue.push(c);
     
     auto zeros = async(launch::async, printerFunc, ref(inputQueue), 1, ref(flagZero));
     auto evens = async(launch::async, printerFunc, ref(inputQueue), 2, ref(flagEven));
     string zerosString = zeros.get();
     string evensString = evens.get();
     cout << "these are the zeros " << zerosString << endl;
     cout << "these are the evens " << evensString << endl;

     this_thread::sleep_for(chrono::seconds(10));
    return 0;

【问题讨论】:

你肯定需要一本关于使用 C++11 进行多线程的好书......你似乎在滥用 std::call_once;您也没有考虑到std::condition_variable::wait 的虚假唤醒,以及许多其他问题.. 我如何滥用 call_once?我想设置一次函数,这就是为什么我有 call_once(flag, [&]()。我想设置一次函数是有原因的,这对手头的问题无关紧要。另一个 call_once 是为了防止第二个线程从甚至开始直到第一个线程允许它。至于condition_variable::wait,我只有一个唤醒线程二的条件,那就是线程一唤醒它。我该如何处理有虚假唤醒 condition_variable::wait 当所有重要的是线程交替时? 如果call_once 对手头的问题无关紧要,那为什么不简化代码呢? 我使用 call_once 的原因无关紧要。我仍然需要使用 call_once。 @WhiZTiM,我尝试添加一个子句来防止虚假唤醒。不过,我不明白我是如何不当使用 call_once 的。 【参考方案1】:

您需要始终检查虚假唤醒条件。使用cond2.wait(lock, [&amp;]()return zeroEvenOddFlag == 1; );之类的帮助检查。此外,您在循环结束时遇到问题。哪个线程最终拥有互斥锁,必须在它消耗队列的最后一位时释放它,以便其他线程可以被释放以完成。最终代码应如下所示:

#include <string>
#include <thread>
#include <future>
#include <cstdio>
#include <iostream>
#include <queue>
#include <condition_variable>

using namespace std;

mutex mu;
condition_variable cond1;
condition_variable cond2;

once_flag flagZero;
once_flag flagEven;
once_flag flagEven1;
int zeroEvenOddFlag = 0;

string printerFunc(queue<char>& input, int zeroEvenOdd, once_flag& flag)
    string output = "";
    function<void(string&)> f;
    unique_lock<mutex> lock(mu);
    call_once(flag, [&]()
        if (zeroEvenOdd == 1)
            f = [&](string& output)
                    int i = input.front() - '0';
                    if (i == 0)
                        output += to_string(i);
                        input.pop();
                        if (input.size() == 0)
                            zeroEvenOddFlag = 1;
                            cond2.notify_all();
                        
                    
                    else 
                        zeroEvenOddFlag = 1;
                        cond2.notify_all();
                        cond1.wait(lock, [&]()return zeroEvenOddFlag == 0; );
                    
            ;
        
        else if (zeroEvenOdd == 2)
            call_once(flagEven1, [&]()cond2.wait(lock, [&]()return zeroEvenOddFlag == 1; ); );
            f = [&](string& output)
                    int i = input.front() - '0';
                    if (i != 0)
                        output += to_string(i);
                        input.pop();
                        if (input.size() == 0)
                            zeroEvenOddFlag = 0;
                            cond1.notify_all();
                        
                    
                    else 
                        zeroEvenOddFlag = 0;
                        cond1.notify_all();
                        cond2.wait(lock, [&]()return zeroEvenOddFlag == 1; );
                    
            ;
        
    );
    while(input.size()!=0)
        f(output);
    
    return output;


int _tmain(int argc, _TCHAR* argv[])

     string input = "0102030";
     queue<char> inputQueue;
     for(char c : input)
         inputQueue.push(c);
     
     auto zeros = async(launch::async, printerFunc, ref(inputQueue), 1, ref(flagZero));
     auto evens = async(launch::async, printerFunc, ref(inputQueue), 2, ref(flagEven));
     string zerosString = zeros.get();
     string evensString = evens.get();
     cout << "these are the zeros " << zerosString << endl;
     cout << "these are the evens " << evensString << endl;

     this_thread::sleep_for(chrono::seconds(10));
    return 0;

【讨论】:

以上是关于尝试使用 condition_variables 在线程之间切换的主要内容,如果未能解决你的问题,请参考以下文章

bool 版本的 boost::condition_variable::wait_until 使用谓词如何表现?

在等待std :: condition_variable时如何处理系统时钟更改?

C++11 condition_variables 可以用来同步进程吗?

跨平台事件处理 - std::condition_variable wait_for 似乎忽略了超时

如何正确使用 std::condition_variable?

std::condition_variable 在 std::condition_variable::notify_all() 从其他线程后未正确唤醒