jed*_*dib 16 c++ multithreading
我只是在阅读Anthony Williams的行动书中的C++并发性.有这个经典的例子有两个线程,一个产生数据,另一个消耗数据,AW写的代码很清楚:
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;
}
Run Code Online (Sandbox Code Playgroud)
我真的不明白为什么这个代码与我使用经典的挥发性bool而不是原子的不同.如果有人能够对这个问题敞开心扉,我将不胜感激.谢谢.
Ben*_*igt 14
最大的区别是这段代码是正确的,而版本bool
代替atomic<bool>
具有未定义的行为.
这两行代码创建了一个竞争条件(正式地说是冲突),因为它们读取和写入同一个变量:
读者
Run Code Online (Sandbox Code Playgroud)while (!data_ready)
和作家
Run Code Online (Sandbox Code Playgroud)data_ready = true;
根据C++ 11内存模型,正常变量的竞争条件会导致未定义的行为.
规则见本标准第1.10节,最相关的是:
如果有两个动作可能是并发的
- 它们由不同的线程执行,或者
- 它们没有排序,至少有一个是由信号处理程序执行的.
程序的执行包含数据竞争,如果它包含两个可能同时发生冲突的动作,其中至少有一个不是原子的,并且除了下面描述的信号处理程序的特殊情况之外,它们都不会发生在另一个之前.任何此类数据争用都会导致未定义的行为.
您可以看到变量是否atomic<bool>
对此规则产生了很大的影响.
bool
正如你所说的那样,"经典" 不会可靠地工作(如果有的话).这样做的一个原因是编译器可以(并且很可能,至少在启用了优化的情况下)data_ready
从内存加载一次,因为没有迹象表明它在上下文中发生了变化reader_thread
.
您可以通过使用volatile bool
每次强制加载它来解决此问题(这似乎可能有效)但是这仍然是关于C++标准的未定义行为,因为对变量的访问既不是同步的也不是原子的.
您可以使用互斥锁头中的锁定工具强制执行同步,但这会(在您的示例中)引入不必要的开销(因此std::atomic
).
问题volatile
是它只保证不会省略指令并保留指令顺序.volatile
不保证内存屏障可以强制执行缓存一致性.这意味着writer_thread
处理器A可以在没有reader_thread
处理器B看到它的情况下将值写入其缓存(甚至可能写入主存储器),因为处理器B的缓存与处理器A的缓存不一致.详细解释请参阅维基百科上的内存障碍和缓存一致性.
然后可能存在更多"复杂"表达式的其他问题x = y
(即x += y
)需要通过锁定(或在这种简单情况下是原子+=
)同步以确保x
在处理期间值不会改变.
x += y
例如实际上是:
x
x + y
x
如果在计算过程中发生了上下文切换到另一个线程,这可能导致类似这样的事情(2个线程,两者都在做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)
Run Code Online (Sandbox Code Playgroud)
现在x = 2
即使有两个+= 2
计算.这种效应被称为撕裂.
小智 7
Ben Voigt 的答案是完全正确的,但仍然有点理论化,当一位同事问我“这对我意味着什么”时,我决定用更实际的答案碰碰运气。
对于您的示例,可能发生的“最简单”优化问题如下:
根据该标准,优化的执行顺序可能不会改变程序的功能。问题是,这只适用于单线程程序或多线程程序中的单线程。
因此,对于 writer_thread 和(易失性)布尔值
data.push_back(42);
data_ready = true;
Run Code Online (Sandbox Code Playgroud)
和
data_ready = true;
data.push_back(42);
Run Code Online (Sandbox Code Playgroud)
是等价的。
结果是,
std::cout << "The answer=" << data[0] << "\n";
Run Code Online (Sandbox Code Playgroud)
可以在不将任何值压入数据的情况下执行。
原子 bool 确实会阻止这种优化,根据定义,它可能不会被重新排序。原子操作有一些标志,允许语句移动到操作前面,但不能移动到后面,反之亦然,但这些需要对编程结构及其可能导致的问题有非常深入的了解......