LWi*_*sey 8 c++ multithreading atomic memory-model c++11
比如说,我Foo在线程#1中创建了一个类型的对象,并希望能够在线程#3中访问它.
我可以尝试这样的事情:
std::atomic<int> sync{10};
Foo *fp;
// thread 1: modifies sync: 10 -> 11
fp = new Foo;
sync.store(11, std::memory_order_release);
// thread 2a: modifies sync: 11 -> 12
while (sync.load(std::memory_order_relaxed) != 11);
sync.store(12, std::memory_order_relaxed);
// thread 3
while (sync.load(std::memory_order_acquire) != 12);
fp->do_something();
Run Code Online (Sandbox Code Playgroud)
Foo,更新为11sync增加到12该场景被破坏,因为线程#3旋转直到它加载12,其可能无序到达(wrt 11)并且Foo不与12一起订购(由于线程#2a中的放松操作).
这有点违反直觉,因为修改顺序sync是10→11→12
标准说(§1.10.1-6):
原子存储 - 释放与从存储中获取其值的load-acquire同步(29.3).[注意:除了在指定的情况下,读取更高的值不一定能确保可见性,如下所述.这样的要求有时会干扰有效的实施. - 尾注]
它也在(§1.10.1-5)中说:
由原子对象M上的释放操作A引导的释放序列是M的修改顺序中的副作用的最大连续子序列,其中第一操作是A,并且每个后续操作
- 由执行A的相同线程执行. ,或
- 是原子读 - 修改 - 写操作.
现在,线程#2a被修改为使用原子读 - 修改 - 写操作:
// thread 2b: modifies sync: 11 -> 12
int val;
while ((val = 11) && !sync.compare_exchange_weak(val, 12, std::memory_order_relaxed));
Run Code Online (Sandbox Code Playgroud)
如果此版本序列正确,Foo则在加载11或12时与线程#3同步.我对使用原子读取 - 修改 - 写入的问题是:
如果是这样:
线程 #2b 的场景是否构成正确的发布顺序?
是的,根据您对标准的引用。
确保此场景正确的读取-修改-写入操作有哪些具体属性?
好吧,有点循环的答案是,唯一重要的特定属性是“C++ 标准如此定义”。
作为一个实际问题,人们可能会问为什么标准会这样定义它。我认为您不会发现答案具有深厚的理论基础:我认为委员会也可以定义它,使 RMW不参与发布序列,或者(可能更困难)定义为RMW以及单独的mo_relaxed加载和存储都参与释放序列,而不影响模型的“健全性”。
他们已经给出了与为什么不选择后一种方法相关的表现:
这样的要求有时会干扰有效的实施。
特别是,在任何允许加载-存储重新排序的硬件平台上,这意味着即使mo_relaxed加载和/或存储也可能需要障碍!如今,这样的平台已经存在。即使在更严格有序的平台上,它也可能会抑制编译器优化。
那么他们为什么不采取其他“一致”的方法,不要求 RMWmo_relaxed参与发布序列呢?可能是因为 RMW 操作的现有硬件实现提供了这样的保证,并且 RMW 操作的性质使得这可能在未来成为现实。特别是,正如 Peter 在上面的评论中指出的那样,RMW 操作即使在mo_relaxed概念上和实践上都比单独的装载和存储强:如果它们没有一致的总顺序,它们将毫无用处。
一旦您接受了硬件的工作原理,从性能角度来看,调整标准就有意义了:如果您不这样做,就会有人使用更严格的顺序,例如只是mo_acq_rel为了获得发布顺序保证,但在真实的硬件上CAS 的顺序很弱,这不是免费的。
1 “实际上”部分意味着即使是最弱形式的 RMW 指令通常也是相对“昂贵”的操作,在现代硬件上需要十几个周期或更多周期,而mo_relaxed加载和存储通常只是编译为目标 ISA 中的普通加载和存储。
| 归档时间: |
|
| 查看次数: |
737 次 |
| 最近记录: |