我想记录一个部分发现,即使这不是一个真正的答案,也不意味着正确的答案不会有很大的赏金。
在盯着 1.10 看了一会儿之后,特别是第 11 段中非常有用的注释,我认为这实际上并不难。synchronizes-with (henceforth: s/w) 和dependency-ordered-before (dob)之间的最大区别在于, happens-before关系可以通过任意连接sequenced-before (s/b) 和 s/w 来建立,但是dob不是这样。请注意线程间的定义之一发生在之前:
A
同步X
并X
在之前排序B
但是类似的语句 for A
is dependency-ordered beforeX
丢失了!
因此,通过发布/获取(即 s/w),我们可以订购任意事件:
A1 s/b B1 Thread 1
s/w
C1 s/b D1 Thread 2
但现在考虑这样的任意事件序列:
A2 s/b B2 Thread 1
dob
C2 s/b D2 Thread 2
A2
在这个序列中, happens-before 仍然是正确的C2
(因为A2
是 s/bB2
和B2
线程间发生在 C2
dob 之前;但我们可以争辩说你永远无法真正分辨!)。但是,happens -before是不正确的。事件和不是相对于彼此排序的,除非它实际上持有对的依赖。这是一个更严格的要求,如果没有这个要求,-to-不能“跨”发布/使用对进行排序。A2
D2
A2
D2
C2
D2
A2
D2
换句话说,发布/消费对只传播动作的顺序,这些动作携带从一个到下一个的依赖关系。不依赖的所有内容都不会在发布/消费对中排序。
此外,请注意,如果我们附加一个最终的、更强大的发布/获取对,则顺序会恢复:
A2 s/b B2 Th 1
dob
C2 s/b D2 Th 2
s/w
E2 s/b F2 Th 3
现在,根据引用的规则,D2
线程间发生 before F2
,因此C2
andB2
也是如此,因此A2
发生之前 F2
。但请注意, and之间仍然没有排序——排序只是介于和之后的A2
事件之间。D2
A2
总之,依赖携带是一般排序的严格子集,并且发布/使用对仅在携带依赖的动作之间提供排序。只要不需要更强的排序(例如通过释放/获取对),理论上就有可能进行额外的优化,因为不在依赖链中的所有内容都可以自由重新排序。
也许这是一个有意义的例子?
std::atomic<int> foo(0);
int x = 0;
void thread1()
{
x = 51;
foo.store(10, std::memory_order_release);
}
void thread2()
{
if (foo.load(std::memory_order_acquire) == 10)
{
assert(x == 51);
}
}
如所写,代码是无竞争的,并且断言将保持,因为释放/获取对x = 51
在断言中的加载之前对存储进行排序。但是,通过将“acquire”更改为“consume”,这将不再是真的,并且程序将在 上发生数据竞争x
,因为x = 51
存储到foo
. 优化点是这个 store 可以自由地重新排序,而不用关心在foo
做什么,因为没有依赖关系。