C++11 条件变量语义
Posted
技术标签:
【中文标题】C++11 条件变量语义【英文标题】:C++11 condition variable semantics 【发布时间】:2020-04-18 13:50:34 【问题描述】:我试图理解std::condition_variable 的语义。我以为我对 C++11 并发模型(原子、内存排序、对应的guarantees and formal relations)有相当的了解,但是关于如何正确使用条件变量的描述似乎与我的理解相矛盾。
TL;DR
reference 说:
打算修改变量的线程必须
获取 std::mutex(通常通过 std::lock_guard) 在持有锁时执行修改 在 std::condition_variable 上执行 notify_one 或 notify_all(通知不需要持有锁)
即使共享变量是原子的,也必须在互斥体下进行修改,才能正确地将修改发布到等待线程。
我明白为什么修改可能必须在释放互斥锁之前完成,但上面似乎很清楚,它必须同时持有互斥锁,即它不能在获取之前。我读对了吗?
更详细
如果我对上述内容的理解是正确的,那么为什么会这样呢?考虑我们在关键部分之前进行修改(通过正确使用原子和锁来确保没有竞争条件)。例如
std::atomic<bool> dummy;
std::mutex mtx;
std::condition_variable cv;
void thread1()
//...
// Modify some program data, possibly in many places, over a long period of time
dummy.store(true, std::memory_order_relaxed); // for simplicity
//...
mtx.lock(); mtx.unlock();
cv.notify_one();
//...
void thread2()
// ...
std::unique_lock<std::mutex> ul(mtx);
cv.wait(ul, []() -> bool
// A complex condition, possibly involving data from many places
return dummy.load(std::memory_order_relaxed); // for simplicity
);
// ...
我的理解是cv.wait()
在继续之前锁定mtx
(检查条件并执行程序的其余部分)。此外,std::mutex::lock()
算作 acquire 操作,std::mutex::unlock()
算作 release 操作。这是否意味着 thread1 中的 unlock() 同步 thread2 中的 lock(),因此在 unlock()
之前在 thread1 中执行的所有原子甚至非原子存储对 thread2 都是可见的它醒来了吗?
Formally: store --sequenced-before--> unlock() --synchronizes-with--> lock() --sequenced-before--> load
...and so: store --happens-before--> load
非常感谢您的任何回答!
[注意:经过大量谷歌搜索后,我还没有找到答案,这很奇怪;如果是重复的,我很抱歉...]
【问题讨论】:
【参考方案1】:考虑在thread1中锁定互斥锁之前的时间和condition_variable首先解锁thread2中的互斥锁之前的时间。
线程1可以
修改大量程序数据dummy.store(true, std::memory_order_relaxed)
线程2可以
锁定互斥锁dummy.load(std::memory_order_relaxed)
(等待前检查谓词)
彼此之间没有顺序。如果 thread2 在此检查中看到 dummy
的真值并继续,则不能保证任何数据修改对 thread2 可见。 thread2 将继续运行,正确地看到了 dummy
的值,但没有正确地看到修改。
您说“通过正确使用原子和锁来确保没有竞争条件”,这是非常开放的。宽松的原子将是正确的,并且修改不一定在 thread2 中可见。但是,围绕这些其他数据修改进行假设的额外同步可以保证可见性。
换句话说,在存储和加载之间应该有一些发布-获取顺序。
这类似于:waiting on worker thread using std::atomic flag and std::condition_variable
【讨论】:
好的,非常感谢。该链接回答了我的问题。 (仅供阅读本文的任何人参考:)我认为您已经错过了互斥锁已锁定和解锁的事实,该互斥锁已经用作释放-获取对(如链接下的第一个答案中所述),所以我' m 现在确信上面的代码是正确的。 @EMarci15 我不认为你在上面的评论中做出了正确的结论。 @EMarci15 你的问题和链接的问题之间的区别是memory_order_relaxed
vs memory_order_release
写入原子标志。
@EMarci15 好的,这很公平。但是,考虑到以这种方式使用条件变量至少可以说是非常“不习惯”的。建议程序员在循环中检查条件等等是有原因的。使条件变量按预期工作是一件棘手的事情。仅从代码审查的角度来看,我建议以这样一种方式设计同步,即您在 T2 中实现的预期效果基于某种状态不变量,该不变量与 T1 是否设法向 T2 发出信号无关。
我认为这只是为了理论理解而对问题过于简单化的一个例子,而不是我的实际代码中的糟糕设计:)以上是关于C++11 条件变量语义的主要内容,如果未能解决你的问题,请参考以下文章
[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)
[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)
[多线程]C++11多线程-条件变量(std::condition_variable)