考虑以下因素:
std::atomic_bool disabled;
int counter1, counter2;
[Thread 1]
counter2 = ...;
if (disabled.load(std::memory_order::acquire) && counter2 > counter1) //#1
{
disabled.store(false, std::memory_order::relaxed);
disabled.notify_one();
}
[Thread 2]
disabled.wait(true, std::memory_order::relaxed);
//Do stuff
counter1 = ...;
disabled.store(true, std::memory_order::release); //#2
这段代码是否保证行为正确,即#1中的加载是否与#2中的存储同步,以便稍后的条件将看到
counter1
的正确值?我可以使用宽松的顺序来等待和存储(假)吗?或者我是否也必须更改它们以获取/释放?
相关场景:
std::atomic_int a = 0;
x = 0;
[Thread 1]
x = 1;
a.store(1, std::memory_order::release);
x = 2;
a.store(2, std::memory_order::relaxed);
[Thread 2]
assert(a.load(std::memory_order::acquire) != 2 || x == 1);
这种情况下断言会失败吗?我能想到两种可能性:
例如,对于
seq_cst
cppreference.com 说:
一旦没有标记为 memory_order_seq_cst 的原子操作进入图片,程序的顺序一致性保证就会丢失
这个问题的上下文是标准/内存模型,而不是特定的架构。
(2) 是彻底的 UB,并且被
-fsanitize=thread
捕获。
第二家商店能够漂浮在第一家商店之上
不可以,不允许线程相对于对“相同”变量的其他访问重新排序对原子变量的访问。仅(在某些情况下)相对于其他变量。
没有重新排序,但在第一个之后发生的存储(放松)使标准中的释放/获取关系无效。或者换句话说:考虑到其他加载/存储之间的顺序较弱,最后发生的发布是否会与获取同步(当然是在同一变量上)?没有同步自 C++20 起
,但这并不重要,因为当 a.store(1, std::memory_order::release);
与某些内容同步时,它只会祝福
之前的操作 (
x = 1
)。由于x = 2
发生在存储之后,它仍然会导致与
x == 1
的数据竞争。
(1) 看起来不错。但是,如果您将第二个线程的内容放入循环中(正如未使用的 wait()
所暗示的那样),那么我相信您会得到 UB(尽管 TSAN 无法检测到)。
counter1 = ...;
发生在
counter2 > counter1
之前,所以这里没有比赛。但是,如果第二个线程处于循环中,当第二次发生 counter1 = ...;
时,就会与前面的
counter2 > counter1
发生数据竞争。将 relaxed
替换为 acquire
/release
可以修复此问题。