0

我这里有一个简单的例子:的std :: conditional_variable :: notify_all不唤醒所有线程

该项目可自从我尝试学习C++ 11个线程调用学术。 这里是对发生了什么事情的描述。

想象一下,一个真正的大std::string有很多的的汇编源代码内像

MOV EBX,ECX; \ r \ nmov EAX,ECX; \ r \ n ....

Parse()函数使用此字符串并通过标记行的开始和结束并将其作为string::const_iterators保存到作业队列中来查找所有行位置。

之后,2个工作线程从队列中弹出此信息并将子字符串解析为IntStuction类对象。他们的push_back教学类的实例,导致进入std::vector<Instruction> result

这里是一个结构声明压阵号和一个子迭代器解析

struct JobItem { 
    int lineNumber; 
    string::const_iterator itStart; 
    string::const_iterator itEnd; 
}; 

这是一个小记录器...

void ThreadLog(const char* log) { 
    writeMutex.lock(); 
    cout << "Thr:" << this_thread::get_id() << " " << log << endl; 
    writeMutex.unlock(); 
} 

这是共享数据:

queue<JobItem> que; 
vector<Instruction> result; 

下面是同步

condition_variable condVar; 
mutex condMutex; 
bool signaled = false; 

mutex writeMutex; 
bool done=false; 
mutex resultMutex; 
mutex queMutex; 

每线程功能

void Func() { 
    unique_lock<mutex> condLock(condMutex); 
    ThreadLog("Waiting..."); 
    while (!signaled) { 
     condVar.wait(condLock); 
    } 
    ThreadLog("Started"); 
    while (!done) { 
     JobItem item; 
     queMutex.lock(); 
     if (!que.empty()) { 
      item = que.front(); que.pop(); 
      queMutex.unlock(); 
     } 
     else { 
      queMutex.unlock(); 
      break; 
     } 
     //if i comment the line below both threads wake up 
     auto instr = ParseInstruction(item.itStart, item.itEnd); 
     resultMutex.lock(); 
     result.push_back(Instruction()); 
     resultMutex.unlock(); 
    } 

管理的线程管理器功能的所有原语......在Func()功能

vector<Instruction> Parser::Parse(const string& instructionStream){ 
    thread thread1(Func); 
    thread thread2(Func); 

    auto it0 = instructionStream.cbegin(); 
    auto it1 = it0; 
    int currentIndex = instructionStream.find("\r\n"); 
    int oldIndex = 0; 
    this_thread::sleep_for(chrono::milliseconds(1000)); //experimental 


    int x = 0; 
    while (currentIndex != string::npos){ 
     auto it0 = instructionStream.cbegin() + oldIndex; 
     auto it1 = instructionStream.cbegin() + currentIndex; 

     queMutex.lock(); 
     que.push({ x,it0,it1 }); 
     queMutex.unlock(); 
     if (x == 20) {//fill the buffer a little bit before signal 
      signaled = true; 
      condVar.notify_all(); 
     } 
     oldIndex = currentIndex + 2; 
     currentIndex = instructionStream.find("\r\n", oldIndex); 
     ++x; 
    } 
    thread1.join(); 
    thread2.join(); 
    done = true; 

    return result; 
} 

问题出现。正如你所看到的,我在里面使用了一些日志记录。和日志说:

Output: 
Thr:9928 Waiting... 
Thr:8532 Waiting... 
Thr:8532 Started 

含义,主线程曾派notify_all()给等待的线程后,只有其中一人居然醒了。 如果我在Func()的内部注释掉ParseInstruction()的呼叫,那么两个线程都会唤醒,否则只有一个线程会这样。 得到一些建议会很好。

+1

你能提供一个[**最小**完整和可验证的例子](http://www.stackoverflow.com/help/mcve)吗? – Barry

+0

对'signaled'的赋值不受'condMutex'保护,所以'Func'可能永远不会看到它。 –

+0

你能更具体吗? – Antiusninja

回答

6

假设Func读取signaled并认为它是假的。

Then Parse sets signaled true and does notify_all;此时Func未在等待,所以没有看到通知。

Func然后等待条件变量和块。

您可以通过将condMutex锁定在signaled的分配周围来避免此情况。

这是正确使用条件变量的正常模式 - 您需要测试和修改您想在同一互斥体中等待的条件。

+1

不要锁定'notify_all()' - 只在作业周围。 – Barry

+0

@Barry谢谢;固定。 –