让我们看一下这个结构:
struct entry {
atomic<bool> valid;
atomic_flag writing;
char payload[128];
}
两个踏板 A 和 B 以这种方式同时访问这个结构(假设e是 的一个实例entry):
if (e.valid) {
// do something with e.payload...
} else {
while (e.writing.test_and_set(std::memory_order_acquire));
if (!e.valid) {
// write e.payload one byte at a time
// (the payload written by A may be different from the payload written by B)
e.valid = true;
e.writing.clear(std::memory_order_release);
}
}
我猜这段代码是正确的并且不会出现问题,但我想了解它为什么会起作用。
引用 C++ 标准(29.3.13):
实现应该使原子存储在合理的时间内对原子负载可见。
现在,记住这一点,想象线程 A 和 B 都进入了else块。这种交错可能吗?
- 两者都
A进入B分支else,因为valid是false A设置writing标志B开始在writing标志上旋转锁定A读取valid标志(即false)并进入if块A写入有效载荷A写入true有效标志;显然,如果再次A阅读valid,它将阅读trueA清除writing标志B设置writing标志B读取有效标志 ( ) 的陈旧值false并进入if块B写入其有效载荷B写true在valid国旗上B清除writing标志
我希望这是不可能的,但是当谈到实际回答“为什么不可能?”这个问题时,我不确定答案。这是我的想法。
再次引用标准(29.3.12):
原子读-修改-写操作应始终读取在与读-修改-写操作关联的写之前写入的最后一个值(按修改顺序)。
atomic_flag::test_and_set()是一个原子读-修改-写操作,如 29.7.5 中所述。
由于atomic_flag::test_and_set()总是读取“新值”,并且我正在使用std::memory_order_acquire内存排序调用它,因此我无法读取标志的陈旧值,因为我必须看到调用之前valid引起的所有副作用(使用)。Aatomic_flag::clear()std::memory_order_release
我对么?
澄清。我的整个推理(错误或正确)依赖于 29.3.12。就我目前的理解而言,如果我们忽略atomic_flag,valid即使它是atomic. atomic似乎并不意味着每个线程“总是立即可见”。您可以要求的最大保证是您读取的值的顺序一致,但您仍然可以在获取新数据之前读取陈旧数据。幸运的是,atomic_flag::test_and_set()每个exchange操作都有这个关键特性:它们总是读取新数据。因此,只有当您在writing标志上获取/释放时(不仅在 上valid),您才会得到预期的行为。你明白我的观点(正确与否)吗?
编辑:我最初的问题包括以下几行,如果与问题的核心相比,这些行引起了太多关注。我留下它们是为了与已经给出的答案保持一致,但如果你现在正在阅读这个问题,请忽略它们。
valid成为 anatomic<bool>而不是 plainboolatomic<bool>有什么意义吗?此外,如果它应该是,那么它不会出现问题的“最小”内存排序约束是什么?