为什么std :: condition_variable的notify和wait函数都需要一个锁定的互斥锁

时间:2015-05-13 08:40:02

标签: c++ multithreading c++11 locking mutex

在我无休止地了解std::contion_variable的过程中,我遇到了以下情况。在this page上,它说明了以下内容:

void print_id (int id) {
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '\n';
}

之后它说:

void go() {
  std::unique_lock<std::mutex> lck(mtx);
  ready = true;
  cv.notify_all();
}

据我了解,这两个函数都会在std::unqique_lock行停止。直到获得一个独特的锁。也就是说,没有其他线程有锁。

所以说首先执行print_id函数。将获取唯一锁定,并且该功能将在等待线上停止。

如果然后执行go函数(在单独的线程上),则其中的代码将在唯一锁定行上停止。由于互斥锁已被print_id函数锁定。

显然,如果代码是这样的话,这将不起作用。但是我真的不知道我没有到达这里。所以请赐教。

3 个答案:

答案 0 :(得分:9)

您遗失的是wait解锁互斥锁,然后等待cv上的信号。

它在返回之前再次锁定互斥锁。

您可以通过点击找到示例的页面上的wait找到这个:

  

在阻塞线程时,该函数自动调用lck.unlock(),允许其他锁定线程继续。

     

一旦被通知(显式地,由其他一些线程通知),该函数解除阻塞并调用lck.lock(),使lck保持与调用函数时相同的状态。

答案 1 :(得分:4)

您错过了wait() 解锁互斥锁的一点。原子线程(释放互斥锁+进入休眠状态)。然后,当被信号唤醒时,它会尝试重新获取互斥锁(可能阻塞);一旦它获得它,它就可以继续。

请注意,没有必要锁定互斥锁以调用notify_*,仅适用于wait*

答案 2 :(得分:0)

回答所提出的问题,这似乎是必要的,因为你不应该因性能原因而获得通知锁定(不正确性比性能更重要吗?):锁定“等待”和推荐的必要性总是锁定“通知”是为了保护用户免受他自己和他的程序的数据和逻辑竞赛。如果没有锁定“go”,您发布的程序将立即在“就绪”上进行数据竞争。但是,即使准备就绪本身也是同步的(例如原子),你会有一个错过通知的逻辑竞赛,因为没有“go”锁定,通知可能会发生检查“就绪”和之前实际等待之后,等待的线程可能会无限期地保持阻塞状态。原子变量本身的同步不足以阻止这种情况。这就是为什么helgrind会在没有按住锁定的情况下发出通知时发出警告的原因。有一些边缘情况,其中通知周围实际上不需要互斥锁。在所有这些情况下,需要事先进行双向同步,以便生产线程可以确定其他线程已经在等待。 IMO这些案例仅供专家使用。实际上,我见过一位专家,谈论多线程,弄错了 - 他认为原子计数器就足够了。也就是说,等待的锁定对于正确性来说总是必要的(或者至少是等待的原子操作),这就是标准库强制执行它并在进入等待时自动解锁互斥锁的原因。

与Windows事件不同,POSIX条件变量不是“白痴”,因为它们是无状态的(除了知道等待线程)。在通知上使用锁定的建议是为了保护您免受最糟糕和最常见的搞砸。如果您愿意,可以使用互斥+条件var + bool变量构建类似Windows的有状态事件。