在我无休止地了解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
函数锁定。
显然,如果代码是这样的话,这将不起作用。但是我真的不知道我没有到达这里。所以请赐教。
答案 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的有状态事件。