C++:__sync_synchronize() 仍然需要 std::atomic?

C++: __sync_synchronize() still needed with std::atomic?

我 运行 陷入了一种不常见但反复出现的竞争状态。 该程序有两个线程并使用 std::atomic。我会将代码的关键部分简化为:

std::atomic<uint64_t> b;  // flag, initialized to 0
uint64_t data[100];  // shared data, initialized to 0

线程 1(发布):

// set various shared variables here, for example
data[5] = 10;

uint64_t a = b.exchange(1);  // signal to thread 2 that data is ready

线程2(接收):

if (b.load() != 0) {  // signal that data is ready
  // read various shared variables here, for example:
  uint64_t x = data[5];
  // race condition sometimes (x sometimes not consistent)
}

奇怪的是,当我向每个线程添加 __sync_synchronize() 时,竞争条件就消失了。我在两个不同的服务器上看到过这种情况。

即当我将代码更改为如下所示时,问题就消失了:

线程 1(发布):

// set various shared variables here, for example
data[5] = 10;

__sync_synchronize();
uint64_t a = b.exchange(1);  // signal to thread 2 that data is ready

线程2(接收):

if (b.load() != 0) {  // signal that data is ready
  __sync_synchronize();
  // read various shared variables here, for example:
  uint64_t x = data[5];
}

为什么需要 __sync_synchronize()?这似乎是多余的,因为我认为交换和加载都确保了逻辑的正确顺序。

架构是 x86_64 个处理器,linux,g++ 4.6.2

虽然不可能从您的简化代码中说出您的实际应用程序中实际发生了什么,但 __sync_synchronize 有帮助的事实以及该函数是内存屏障的事实告诉我您正在编写一个线程中另一个线程正在读取的东西,以一种非原子的方式。

一个例子:

thread_1:

    object *p = new object;
    p->x = 1;
    b.exchange(p);   /* give pointer p to other thread */

thread_2:

    object *p = b.load();
    if (p->x == 1) do_stuff();
    else error("Huh?");

这很可能会触发thread2中的error-path,因为当thread 2读取新的指针值p.

时,对p->x的写入实际上还没有完成

添加内存屏障,在这种情况下,在 thread_1 代码中应该可以解决这个问题。请注意,对于这种情况,thread_2 中的内存屏障不会做任何事情 - 它可能会改变时间并似乎解决了问题,但这不是正确的事情。如果你是 reading/writing 两个线程共享的内存,你可能仍然需要两侧的内存屏障。

我知道这可能不完全是您的代码在做什么,但概念是相同的 - __sync_synchronize 确保在该函数调用之前所有指令的内存读取和内存写入已完成 [这不是真正的函数调用,它将内联一条等待任何未决内存操作完成的指令。

值得注意的是,对 std::atomic 的操作只会影响存储在原子对象中的实际数据。不是 reads/writes 的其他数据。

有时您还需要 "compiler barrier" 来避免编译器将操作的一侧移动到另一侧:

  std::atomic<bool> flag(false);
  value = 42;
  flag.store(true);

  ....

另一个话题:

  while(!flag.load());
  print(value); 

现在,编译器有可能生成第一种形式:

  flag.store(true);
  value = 42;

现在,那不是很好,是吗? std::atomic 保证是 "compiler barrier",但在其他情况下,编译器可能会以类似的方式随机排列内容。