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

时间:2018-02-14 17:25:03

标签: c++ linux atomicity

我一直遇到一种罕见但又重新出现的竞争状态。 该程序有两个线程,使用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

1 个答案:

答案 0 :(得分: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中的错误路径,因为当线程2读取新指针值p时,对p->x的写入实际上并未完成。

在这种情况下,在thread_1代码中添加内存屏障应该解决这个问题。请注意,对于这种情况,thread_2中的内存屏障不会执行任何操作 - 它可能会改变时间并且似乎可以解决问题,但这不是正确的事情。如果您正在读/写两个线程之间共享的内存,则可能还需要双方的内存屏障。

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

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

有时你还需要一个“编译器障碍”来避免编译器将操作从一端移动到另一端:

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

  ....

另一个主题:

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

现在,编译器有可能生成第一个表单:

  flag.store(true);
  value = 42;

现在,这不会是好事,不是吗? std :: atomic保证是一个“编译器障碍”,但在其他情况下,编译器可能会以类似的方式改变原因。