我正在阅读 Anthony Williams 的 C++ concurrency in action book。 这个经典的例子有两个线程,一个产生数据,另一个消耗数据,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
,正如你所说,它不能可靠地工作(如果有的话)。造成这种情况的一个原因是编译器可以(并且很可能会,至少在启用优化的情况下)加载 data_ready
只有一次来自内存,因为没有迹象表明它会在 reader_thread
的上下文中发生变化.
您可以使用 volatile bool
来解决这个问题。每次都强制加载它(这可能看起来可行),但这仍然是关于 C++ 标准的未定义行为,因为对变量的访问既不是同步的也不是原子的。
您可以使用 mutex header 中的锁定工具强制同步。 ,但这会引入(在您的示例中)不必要的开销(因此 std::atomic
)。
volatile
的问题是它只保证不遗漏指令并保留指令顺序。 volatile
不保证使用内存屏障来强制缓存一致性。这意味着 writer_thread
在处理器 A 上,无需 reader_thread
就可以将值写入它的缓存(甚至可能写入主内存)在处理器 B 上看到它,因为处理器 B 的缓存与处理器 A 的缓存不一致。更详尽的解释见 memory barrier和 cache coherence在维基百科上。
比 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)
现在 x = 2
即使有两个 += 2
计算。这种效果被称为撕裂。
关于C++:std::atomic<bool> 和 volatile bool,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29633222/