考虑以下 C++23 程序:
#include <atomic>
#include <cassert>
#include <thread>
#define LOAD_ORDER std::memory_order_seq_cst
#define STORE_ORDER std::memory_order_seq_cst
std::atomic_int v[2]{0, 0};
int r[2];
void
f(int i)
{
v[i].store(1, STORE_ORDER);
r[i] = v[1-i].load(LOAD_ORDER);
}
int
main()
{
{
std::jthread t0(f, 0);
std::jthread t1(f, 1);
}
assert(r[0] || r[1]);
return 0;
}
一个线程将 1 存储到
v[0]
,然后加载 v[1]
。 同时,另一个线程将 1 存储到 v[1]
,然后加载 v[0]
。 我想确保至少一次加载返回 1。我的问题:
在较高级别上,顺序一致的操作无法重新排序,因此这应该可行。 但是,如何仅使用语言规范中的在之前排序、与同步、线程间发生在之前以及可见副作用概念来证明上述代码是正确的? 例如,一家商店似乎不会出现在另一家商店之前。
如果我将
LOAD_ORDER
更改为 std::memory_order_relaxed
并将 STORE_ORDER
保留为 std::memory_order_seq_cst
,代码仍然正确吗? 放松 STORE_ORDER
但不施加负荷的反面呢?
我可以设置
LOAD_ORDER
和 STORE_ORDER
并且仍然保证断言不会失败的最宽松或最有效的值是多少?
假设所有变量都位于不同的缓存行上——我只是没有添加填充以使示例更简单。 还假设没有消耗操作,所以我们可以忽略之前发生的弱/强/普通之间的区别。
所有具有顺序一致性的原子操作都以全局总顺序发生。
因此,您有存储到
v[0]
和 v[1]
,我们称它们为 S0
和 S1
,并类似地加载 L0
和 L1
。
这个顺序需要与sequenced-with关系一致。在第一个线程中,
S0
在L1
之前排序,在第二个线程中,S1
在L2
之前排序,即S0 < L1
和S1 < L0
。
在全局总顺序中,您有
L0 < L1
或 L1 < L0
。
L0 < L1
与 S1 < L0
一起意味着 S1 < L1
,即 L1
将加载 1
。对于 L1 < L0
,您同样会得到 L0
将加载 1
。
无论哪种方式,断言都得到满足。
如果削弱内存排序,则断言将不再得到保证。