考虑以下使用
std::memory_order_consume
同步数据的示例:
struct X
{
int i;
std::string s;
};
std::atomic<X*> p;
std::atomic<int> a;
void create_x()
{
X* x=new X;
x->i=42;
x->s="hello";
a.store(99,std::memory_order_relaxed); //1
p.store(x,std::memory_order_release); //2
}
void use_x()
{
X* x;
while(!(x=p.load(std::memory_order_consume))) //3
std::this_thread::sleep(std::chrono::microseconds(1));
assert(x->i==42); //4
assert(x->s=="hello"); // 5
assert(a.load(std::memory_order_relaxed)==99); // 6
}
int main()
{
std::thread t1(create_x);
std::thread t2(use_x);
t1.join();
t2.join();
}
Anthony Williams 在他的《Concurrency in Action》一书中给出了以下解释:
即使对 a 1 的存储在对 p 2 的存储之前排序,并且 p 的存储标记为 memory_order_release,p 3 的负载为 标记为内存_顺序_消耗。这意味着商店只能 发生在依赖于值的表达式之前 从p加载。这意味着对数据成员的断言 X结构(4和5)保证不会着火,因为 p 通过变量 x 承载对这些表达式的依赖。在 另一方面,对 6 值的断言可能会触发,也可能不会触发; 此操作不依赖于从 p 加载的值,因此 无法保证所读取的值。这一点特别 很明显,因为它被标记为 memory_order_relaxed。
这个解释是有道理的,但是当我尝试根据cppreference定义的发生之前关系来思考它时,我无法得出相同的结论:
最后的说法似乎与书中所写的相矛盾。我的逻辑链是否有错误,或者存储 1 发生在加载 6 之前并不一定意味着 1 的影响对 6 可见?
你的逻辑错误在最后一步。 C++23(N4950 最终草案)中发生之前的定义是:
评估 A 发生在评估 B 之前(或者等效地,B 发生在 A) 之后,如果:
— A 排在 B 之前,或者
— 线程间 发生在 B 之前。
注意,有not第三个子句说“如果A发生在X之前并且X发生在B之前”。 发生之前关系not定义为传递性。
所以确实 1 发生在 3 之前并且 3 发生在 6 之前,但并不意味着 1 发生在 6 之前。
还要注意,您无法显示 1 个线程间发生在 6 个线程之前。
(当没有消耗操作时,因此没有操作在任何其他操作之前都是依赖排序的,那么您可以证明发生之前是传递性的,并且是sequenced-before和synchronized-with并集的传递闭包。这匹配 simple-happens-before 的定义方式,并且有一个注释指出了这一点。)