C++:标准::原子<bool>和挥发性布尔值
C++ : std::atomic<bool> and volatile bool
我正在阅读Anthony Williams的《C++并发操作手册》。有一个经典的例子,有两个线程,一个产生数据,另一个消耗数据,A.W.非常清楚地写下了代码:
std::vector<int> data;
std::atomic<bool> data_ready(false);
void reader_thread()
{
while(!data_ready.load())
{
std::this_thread::sleep(std::milliseconds(1));
}
std::cout << "The answer=" << data[0] << "n";
}
void writer_thread()
{
data.push_back(42);
data_ready = true;
}
我真的不明白为什么这段代码与我使用经典的volatile bool而不是原子bool的代码不同。如果有人能让我敞开心扉,我将不胜感激。谢谢
A"经典的";正如您所说,bool
不会可靠地工作(如果有的话)。其中一个原因是编译器只能从内存中加载data_ready
一次(而且很可能是这样,至少在启用了优化的情况下),因为没有迹象表明它在reader_thread
的上下文中会发生变化。
您可以通过使用volatile bool
强制每次加载它来解决这个问题(这可能看起来有效),但这仍然是C++标准中未定义的行为,因为对变量的访问既不是同步的,也不是原子的。
您可以使用互斥体头中的锁定功能来强制同步,但这会引入(在您的示例中)不必要的开销(因此std::atomic
)。
volatile
的问题在于,它只保证指令不被省略,并且保留指令顺序。volatile
不能保证内存屏障来强制缓存一致性。这意味着处理器A上的writer_thread
可以将值写入其缓存(甚至可能写入主内存),而处理器B上的reader_thread
看不到它,因为处理器B的缓存与处理器A的缓存不一致。有关更全面的解释,请参阅维基百科上的内存屏障和缓存一致性。
与x = y
(即x += y
)相比,更复杂的表达式可能存在额外的问题,需要通过锁(或者在这种简单的情况下是原子+=
)进行同步,以确保x
的值在处理过程中不会改变。
例如x += y
实际上是:
- 读取
x
- 计算
x + y
- 将结果写回
x
如果在计算过程中发生上下文切换到另一个线程,这可能会导致类似的情况(两个线程都在执行x += 2
;假设为x = 0
):
Thread A Thread B
------------------------ ------------------------
read x (0)
compute x (0) + 2
<context switch>
read x (0)
compute x (0) + 2
write x (2)
<context switch>
write x (2)
现在CCD_ 20,尽管有两个CCD_。这种效应被称为撕裂。
最大的区别是这个代码是正确的,而使用bool
而不是atomic<bool>
的版本有未定义的行为。
这两行代码创建了一个竞争条件(形式上是冲突),因为它们读取和写入同一个变量:
阅读器
while (!data_ready)
作家
data_ready = true;
根据C++11内存模型,正常变量上的竞争条件会导致未定义的行为。
规则见本标准第1.10节,最相关的是:
如果
- 它们由不同的线程执行,或者
- 它们是无序列的,并且至少有一个是由信号处理器执行的
如果程序的执行包含两个潜在的并发冲突操作,其中至少一个操作不是原子操作,并且两个操作都不在另一个操作之前发生,则程序的执行将包含数据竞赛,以下所述的信号处理程序的特殊情况除外。任何这样的数据竞赛都会导致未定义的行为。
您可以看到变量是否为atomic<bool>
对该规则有很大的影响。
Ben Voigt的答案是完全正确的,仍然有点理论性,当一位同事问我"这对我意味着什么"时,我决定用一个更实用的答案来碰碰运气。
对于您的样本,可能出现的"最简单"的优化问题如下:
根据该标准,优化的执行顺序可能不会改变程序的功能。问题是,这仅适用于单线程程序或多线程程序中的单线程。
因此,对于writer_thread和(易失性)bool
data.push_back(42);
data_ready = true;
和
data_ready = true;
data.push_back(42);
是等效的。
结果是,
std::cout << "The answer=" << data[0] << "n";
可以在不将任何值推入数据的情况下执行。
原子布尔确实阻止了这种优化,根据定义,它可能不会被重新排序。原子操作有一些标志,允许语句移动到操作的前面,但不能移动到后面,反之亦然,但这些标志需要对编程结构及其可能导致的问题有非常深入的了解。。。
- 在没有定义返回类型的函数中返回布尔值,并将结果保存在无错误的char编译中-为什么
- 变量定义到C++布尔值转换
- 如何确保在使用基于布尔值的两个方法之一调用方法时避免分支预测错误
- 重载更少,则运算符返回相反的布尔值
- 将此布尔值传递给此函数的最有效方法是什么?
- 如何设置 c++ 类的布尔值?
- 使用 MAKEWORD / MAKEWPARAM 使用布尔值而不是布尔值
- 将 10 个线程与原子布尔值同步
- 创建类似于布尔值的变量类型
- 标准::时间::d类型的挥发性对象
- 布尔值向量的基于范围 for 循环
- 零点和布尔值之间的比较
- 简化对两个布尔值的 4 个 if/else 检查
- 无法创建带有布尔值和矢量的地图
- 对于完成布尔值设置为 true 后未停止的循环
- fstream / ifstream / ofstream 对象如何转换为布尔值
- C++:将值赋值给原始数据类型(例如布尔值)是原子操作吗?
- 为什么布尔值不能比作最后一点?
- 为什么 std::atomic<bool> 比挥发性布尔值慢得多?
- C++:标准::原子<bool>和挥发性布尔值