std::atomic 和 std::condition_variable 等待、通知_* 方法之间的区别
Difference between std::atomic and std::condition_variable wait, notify_* methods
我正在浏览 'Atomic operations library' 并发现了原子 'wait' 和 'notify_' 方法的一个新的 c++20 特性。我很好奇 std::condition_variable 的 'wait' 和 'notify_' 方法有什么区别。
std:atomic wait
、notify_all
和 notify_one
方法类似于条件变量的方法。它们允许通过使用更高效和轻量级的原子变量而不使用互斥锁来实现以前需要条件变量的逻辑。
wait
函数阻塞线程,直到原子对象的值修改。它需要一个参数来与原子对象的值进行比较。并反复执行:
- 如果值相等,它会阻塞线程,直到收到
notify_one
或 notify_all
的通知,或者线程被虚假地解除阻塞。
- 否则,returns.
注意:仅当值已更改时,wait
才保证为 return,即使底层实现虚假地解锁也是如此。
您可以在此处找到实现:https://github.com/ogiroux/atomic_wait/。
策略是这样选择的,按平台:
- Linux:默认为 futex(有 table),回退到 futex(没有 table)-> CVs -> timed backoff -> spin.
- Mac:默认为 CVs (table),回退到定时退避 -> 自旋。
- Windows:默认为 futex(无 table),回退到定时退避 -> 自旋。
- CUDA:默认为定时退避,回退到自旋。 (这并没有全部签入这棵树。)
- 未识别的平台:默认旋转。
在整个使用模式上存在差异。
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();
我正在浏览 'Atomic operations library' 并发现了原子 'wait' 和 'notify_' 方法的一个新的 c++20 特性。我很好奇 std::condition_variable 的 'wait' 和 'notify_' 方法有什么区别。
std:atomic wait
、notify_all
和 notify_one
方法类似于条件变量的方法。它们允许通过使用更高效和轻量级的原子变量而不使用互斥锁来实现以前需要条件变量的逻辑。
wait
函数阻塞线程,直到原子对象的值修改。它需要一个参数来与原子对象的值进行比较。并反复执行:
- 如果值相等,它会阻塞线程,直到收到
notify_one
或notify_all
的通知,或者线程被虚假地解除阻塞。 - 否则,returns.
注意:仅当值已更改时,wait
才保证为 return,即使底层实现虚假地解锁也是如此。
您可以在此处找到实现:https://github.com/ogiroux/atomic_wait/。
策略是这样选择的,按平台:
- Linux:默认为 futex(有 table),回退到 futex(没有 table)-> CVs -> timed backoff -> spin.
- Mac:默认为 CVs (table),回退到定时退避 -> 自旋。
- Windows:默认为 futex(无 table),回退到定时退避 -> 自旋。
- CUDA:默认为定时退避,回退到自旋。 (这并没有全部签入这棵树。)
- 未识别的平台:默认旋转。
在整个使用模式上存在差异。
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();