条件变量对于
notify()
和 unlock_sleep()
(在 wait()
中使用的虚构函数调用,其中互斥体被解锁并且线程作为一个原子操作序列休眠)操作应该具有单一顺序。为了使用任意可锁定来实现这一点std::condition_variable_any
实现通常在内部使用另一个互斥锁(以确保原子性并继续休眠)
如果内部
unlock_sleep()
和 notify()
(notify_one()
或 notify_all()
)操作相对于彼此而言不是原子的,那么您将面临一个线程解锁互斥体、另一个线程发信号、然后原始线程进入睡眠状态并且永远不会发生的风险。醒来了。
我正在阅读 std::condition_variable_any 的 libstdc++ 和 libc++ 实现,并注意到 libc++ 实现中的这段代码
{lock_guard<mutex> __lx(*__mut_);}
__cv_.notify_one();
内部互斥体被锁定,然后在信号操作之前立即解锁。这不会带来我上面描述的问题的风险吗?
C++11 及更高版本的标准明确规定“
notify_one
和 notify_all
的执行应是原子的”。因此,从某种意义上说,我认为您是正确的,内部互斥体应该在调用过程中保持到平台的底层条件变量通知调用(例如pthread_cond_signal()
)
但是,我不认为 libc++ 实现会导致错过通知,因为如果没有通知线程在锁上同步,则等待线程在调用
wait()
时会传递到 notify_one()
(或两个线程之间的其他同步) (或notify_all()
)无论如何,都无法确保两个线程中的哪一个是“第一个”通知或等待。因此,如果在 libc++ 的当前实现中可能会错过通知,那么如果将 libc++ 更改为在对平台通知 API 的调用中保持内部锁定,也可能会错过通知。
所以我认为libc++可以调用“好像”规则来表示
notify_one()
/notify_any()
的实现“足够原子”。
我不太清楚你的问题,但我们传递给
wait
的函数的目的是检查是否满足所需的条件。如果不是,wait
将解锁外部锁并继续等待,直到发生(可能是虚假的)唤醒。
根据 libc++ 中的实现,如 C++ 的当前工作草案中所述(重点是我的):
和notify_one
的执行是原子的。notify_all
、wait
和wait_for
的执行是在三个原子部分中执行的:wait_until
- 释放互斥体并进入等待状态。
- 等待的畅通;和
- 重新获取锁。
该实现的行为就好像
、notify_one
的所有执行以及notify_all
、wait
和wait_for
执行的每个部分都以单个 未指定 总顺序执行,与“发生”一致之前”订单。wait_until
wait
的第一部分(解锁和等待)的原子性是由condition_variable_any
的内部锁保证的。在notify_one
和notify_all
中,我们必须首先获取内部锁,以确保解锁-等待组合正确执行。 condition_variable_any
的其他部分已经是原子的,这要归功于内部的condition_variable
,这有助于维持这种原子性,使得notify_one
和notify_all
中的内部锁能够立即释放。