memory_order_seq_cst 输出 10

output 10 with memory_order_seq_cst

当我 运行 这个程序时,我得到的输出是 10,这对我来说似乎是不可能的。我 运行 在 x86_64 core i3 ubuntu 上安装这个 ubuntu。

如果输出为 10,则 1 必须来自 c 或 d。

同样在线程 t[0] 中,我们将 c 赋值为 1。现在 a 为 1,因为它出现在 c=1 之前。 c 等于 b 被线程 1 设置为 1。所以当我们存储 d 时,它应该是 1 as a=1.


取消对 fence 的注释不起作用。 尝试了 运行 g++clang++。两者给出相同的结果。

#include<thread>
#include<unistd.h>
#include<cstdio>
#include<atomic>
using namespace std;

atomic<int> a,b,c,d;

void foo(){
        a.store(1,memory_order_seq_cst);
//        atomic_thread_fence(memory_order_seq_cst);
        c.store(b,memory_order_seq_cst);
}

void bar(){
        b.store(1,memory_order_seq_cst);
  //      atomic_thread_fence(memory_order_seq_cst);
        d.store(a,memory_order_seq_cst);
}

int main(){
        thread t[2];
        t[0]=thread(foo); t[1]=thread(bar);
        t[0].join();t[1].join();
        printf("%d%d\n",c.load(memory_order_seq_cst),d.load(memory_order_seq_cst));
}
bash$ while [ true ]; do ./a.out | grep "10" ; done 
10
10
10
10

printf 语句是不同步的,因此 10 的输出可能只是重新排序的 01。
01 发生在 printf 运行 之前的函数串行时。

10 (c=1, d=0) 很容易解释:bar先发生在运行,并在foo读到b之前完成。

让线程在不同内核上启动的内核间通信的怪癖意味着即使 thread(foo) 运行 首先在主线程中也很容易发生这种情况。例如也许中断到达了 OS 为 foo 选择的核心,延迟了它实际进入该代码 1.

请记住,seq_cst 仅 gua运行 证明所有 seq_cst 操作都存在某种总顺序,该顺序与每个线程中的前序顺序兼容。 (以及由其他因素建立的任何其他先于发生的关系)。因此,以下原子操作顺序是可能的,甚至无需将 bar 中的 a.load2 与结果 int temporary.d.store 分开。

        b.store(1,memory_order_seq_cst);   // bar1.  b=1
        d.store(a,memory_order_seq_cst);   // bar2.  a.load reads 0, d=0

        a.store(1,memory_order_seq_cst);   // foo1
        c.store(b,memory_order_seq_cst);   // foo2.  b.load reads 1, c=1
// final: c=1, d=0

atomic_thread_fence(seq_cst) 对任何地方都没有影响,因为您的所有操作都已经 seq_cst. 栅栏基本上只是停止对该线程的操作进行重新排序;它不会等待或与其他线程中的栅栏同步。

(只有看到另一个线程存储的值的加载才能创建同步。但是这样的加载不会等待另一个存储;它无法知道还有另一个商店。如果你想一直加载直到看到你期望的值,你必须写一个自旋等待循环。)


脚注 1: 由于您所有的原子变量可能都在同一个缓存行中,即使执行确实在两个不同的内核上同时到达 foobar 的顶部,错误共享也可能会让两者来自一个线程的操作在另一个核心仍在等待获得独占所有权时发生。尽管 seq_cst 存储速度足够慢(至少在 x86 上)硬件公平的东西可能会在提交 1 的第一个存储后放弃独占所有权。不管怎样,有很多方法可以让一个线程中的两个操作在另一个线程之前发生并得到 10 或 01。如果我们得到 b=1,甚至有可能得到 11然后 a=1 在任一加载之前。使用 seq_cst 确实会阻止硬件提前加载(在存储全局可见之前),所以这是很有可能的。

脚注 2:bare a 的左值到右值求值使用重载的 (int) 转换,相当于 a.load(seq_cst)。来自 foo 的操作可能 发生在该负载和从中获取临时值的 d.store 之间。 d.store(a) 不是原子副本;它等同于 int tmp = a; d.store(tmp);。这没有必要解释您的观察结果。