std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别

Posted

技术标签:

【中文标题】std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别【英文标题】:Difference between std::atomic and std::condition_variable wait, notify_* methods 【发布时间】:2020-11-01 16:32:27 【问题描述】:

我在浏览“原子操作库”时发现了原子“等待”和“通知_”方法的新 c++20 功能。我很好奇 std::condition_variable 的 'wait' 和 'notify_' 方法有什么区别。

【问题讨论】:

区别在于实现定义。除了界面之外,可能根本没有区别。我认为原子版本将通过更轻量级的方案或其他方式实现,或者相反,它可能会执行更多的旋转或它在开始时所做的任何事情。 【参考方案1】:

std:atomic waitnotify_allnotify_one 方法类似于条件变量的方法。它们允许通过使用更高效、更轻量级的原子变量而不使用互斥锁来实现以前需要条件变量的逻辑。

wait 函数阻塞线程,直到原子对象的值被修改。它需要一个参数来与原子对象的值进行比较。它反复执行:

如果值相等,它会阻塞线程直到notify_onenotify_all 通知,或者线程被虚假解除阻塞。 否则,退货。

注意:wait 保证仅在值发生更改时返回,即使底层实现虚假地解除阻塞。


您可以在此处找到实现:https://github.com/ogiroux/atomic_wait/。

策略是这样选择的,按平台:

Linux:默认为 futex(有表),回退到 futex(无表)-> CVs -> 定时退避 -> 自旋。 Mac:默认为 CVs(表格),回退到定时退避 -> 旋转。 Windows:默认为 futex(无表),回退到定时退避 -> 自旋。 CUDA:默认为定时退避,回退到自旋。 (此树中并未全部签入。) 不明平台:默认为旋转。

【讨论】:

一个实现,我想说。虽然它是proposal 实现,但作为C++2a 特性,它是在STL 实现中实现的。真正的实现可以更复杂(在 Windows 上使用 CV 回退也很有用),或者更少(此实现在通知 futex 之前进行检查,但不是强制性的)。【参考方案2】:

在整个使用模式方面存在差异。

condition_variable 等待需要互斥锁。在通知之前应该使用相同的互斥锁:

std::mutex mtx;
std::condition_variable cv;

bool condition();
void change_condition();

...

std::unique_lock<std::mutex> lock(mtx);
while (!condition())

   cv.wait(lock);


...

std::unique_lock<std::mutex> lock(mtx);
change_condition();
lock.unlock();
cv.notify_one();

现在如果你有条件变量的原子,你仍然需要锁:

std::mutex mtx;
std::condition_variable cv;

std::atomic<bool> condition;

...

std::unique_lock<std::mutex> lock(mtx);
while (!condition.load())

   cv.wait(lock);


...

std::unique_lock<std::mutex> lock(mtx);
condition.store(true);
lock.unlock();
cv.notify_one();

Atomic 本身不需要带锁的保护,因此可以在不加锁的情况下对其进行修改。但是,仍然需要互斥锁来与等待同步并避免丢失唤醒。唤醒线程的替代方法如下:

condition.store(true);
std::unique_lock<std::mutex> lock(mtx);
lock.unlock();
cv.notify_one();

不能省略互斥锁,即使在通知端也是如此。

(而且你无法摆脱 condiion_variable_any 和在其 lock / unlock 中什么都不做的“空互斥锁”)。


现在,原子等待。 除了在另一个答案中提到的没有虚假唤醒之外,不需要互斥锁:


std::atomic<bool> condition;

...

condition.wait(false);

...

condition.store(true);
condition.notify_one();

【讨论】:

以上是关于std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

tbb::atomic和std::atomic的区别 废弃

std::atomic 和多核处理器

std::atomic<std::string> 是不是正常工作?

std::atomic 库依赖 (gcc 4.7.3)

互锁变量访问(在布尔值上)和 std::atomic_flag 之间的区别

std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别