今天的许多编程语言都有happens-before
关系和release+acquire
同步操作。
其中一些编程语言:
我想知道是否release+acquire
可以违反happens-before
:
- 如果可能的话,那么我想看一个例子
- 如果不可能,那么我想得到简单明了的解释为什么
什么是release+acquire
和happens-before
Release/acquire
建立happens-before
不同线程之间的关系:换句话说,之前release
in的所有内容都Thread 1
保证在Thread 2
after中可见acquire
:
\ Thread 1 /
\ -------- /
\ x = 1 / Everything
\ y = 2 / here...
\ write-release(ready = true) /
└───────────────────────────┘
|
└─────────────┐ (happens-before)
V
┌─────────────────────────┐
/ Thread 2 \
...is visible to / -------- \
everything / read-acquire(ready == true) \
here / assert(x == 1) \
/ assert(y == 2) \
不仅如此,happens-before
还是严格的偏序。
这意味着它是:
- 传递性:
Thread 2
保证不仅可以看到 的写入Thread 1
,还可以Thread 1
看到其他线程的所有写入 - 不对称:
a
happens-beforeb
或b
happens-beforea
都不允许
为什么我认为这release/acquire
可能会破坏happens-before
正如我们从 IRIW 石蕊测试中知道的那样,release/acquire
可能导致两个线程以不同的顺序查看来自不同线程的写入(对于 C++,另请参见此处的最后一个示例,以及来自 gcc wiki的这 两个示例):
// Thread 1
x.store(1, memory_order_release);
// Thread 2
y.store(1, memory_order_release);
// Thread 3
assert(x.load(memory_order_acquire) == 1 && y.load(memory_order_acquire) == 0)
// Thread 4
assert(y.load(memory_order_acquire) == 1 && x.load(memory_order_acquire) == 0)
这里两个assert
s 都可以通过,这意味着Thread 3
和see 以不同的顺序Thread 4
写入。x
y
据我了解,如果它是普通变量,那么这将违反happens-before的不对称属性。但是因为 x 和 y 是原子的,所以没关系。(顺便说一句,我不确定)
Nate Eldredge在他的回答中证明这个 IRIW 示例是可以的。
但我仍然有一个偷偷摸摸的怀疑,可能存在类似于 IRIW 的东西,它会导致Thread 3
并Thread 4
看到常规写入以不同的顺序发生在发生之前——这会破坏发生在之前(它不再是传递的)。
注1
在cppreference中也有这样的引用:
该实现需要通过在必要时引入额外的同步来确保发生之前的关系是非循环的(只有在涉及消费操作时才需要,参见 Batty 等人)
引用暗示可能存在happens-before
违反并需要额外同步的情况(“无环”意味着发生之前形成有向无环图,相当于说“严格偏序”)。
如果可能的话,我想知道这些情况是什么。
笔记2
由于 java 允许数据竞争,我也对happens-before
仅在存在数据竞争时违反的情况感兴趣。
编辑 1(2021 年 11 月 3 日)
举个例子,这里解释了为什么顺序一致(SC)原子不能违反happens-before
.
(对释放/获取原子的类似解释将是我问题的答案)。
“违反happens-before
”我的意思是“违反 的公理happens-before
,这是一个严格的偏序”。
严格的偏序直接对应于有向无环图 (DAG)。
这是来自 wiki 的 DAG 示例(请注意,它没有循环):
让我们证明 SC 原子happens-before
图保持非循环。
请记住,SC 原子以全局顺序发生(所有线程都相同),并且:
- 顺序与每个线程内的动作顺序一致
- 每个 SC 原子读取都会以这个总顺序看到最新的 SC 原子写入到同一个变量
看这张happens-before
图:
Thread1 Thread2 Thread3
======= ======= =======
│ │ │
W(x) │ │
↓ │ │
Sw(a) ┐ │ W(y)
│ │ │ ↓
│ └> Sr(a) ┌ Sw(b)
│ ↓ │ │
│ Sr(b)<─┘ │
│ ↓ │
│ R(x) │
│ ↓ │
│ R(y) │
│ │ │
V V V
在图表上:
- 时间向下流动
W(x)
并且R(x)
是常规动作:写入和读取x
Sw(a)
并且Sr(a)
是 SC 原子:写入和读取a
- 在每个线程内,操作按程序顺序(
sequenced-before order
在 C++ 中也称为)发生:按照它们在代码中的顺序 - 线程之间
happens-before
由 SC atomics 建立
请注意,图上的箭头始终向下
=> 图不能有环
=> 它始终是 DAG
=>happens-before
不能违反公理
相同的证明不适用于release/acquire
原子,因为(据我所知)它们不会以全局顺序发生=>之间的HB箭头Sw(a)
并且Sr(a)
可能向上=>可能存在循环。(对此我不确定)