我遇到了一种罕见但反复发生的竞争状况。该程序有两个线程并使用 std::atomic。我将简化代码的关键部分,如下所示:
std::atomic<uint64_t> b; // flag, initialized to 0
uint64_t data[100]; // shared data, initialized to 0
Run Code Online (Sandbox Code Playgroud)
线程 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
Run Code Online (Sandbox Code Playgroud)
线程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)
}
Run Code Online (Sandbox Code Playgroud)
奇怪的是,当我向每个线程添加 __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
Run Code Online (Sandbox Code Playgroud)
线程2(接收):
if (b.load() != 0) { // signal that data is ready
__sync_synchronize();
// read various shared variables here, for example:
uint64_t x = data[5];
}
Run Code Online (Sandbox Code Playgroud)
为什么需要 __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?");
Run Code Online (Sandbox Code Playgroud)
这很可能会触发线程 2 中的错误路径,因为当线程 2 读取新的指针值 p 时,写入p->x实际上尚未完成。
在本例中,在 thread_1 代码中添加内存屏障应该可以解决此问题。请注意,对于这种情况,thread_2 中的内存屏障不会做任何事情 - 它可能会改变时间并似乎解决问题,但这不是正确的事情。如果您正在读取/写入两个线程之间共享的内存,则两侧可能仍然需要内存屏障。
我知道这可能不完全是您的代码正在做的事情,但概念是相同的 -__sync_synchronize确保在该函数调用之前所有指令的内存读取和内存写入已完成[这不是真正的函数调用,它将内联一条等待任何挂起的内存操作完成的指令]。
值得注意的是,对std::atomic原子对象的操作仅影响存储在原子对象中的实际数据。不读取/写入其他数据。
有时您还需要“编译器屏障”来避免编译器将内容从操作的一侧移动到另一侧:
std::atomic<bool> flag(false);
value = 42;
flag.store(true);
....
Run Code Online (Sandbox Code Playgroud)
另一个线程:
while(!flag.load());
print(value);
Run Code Online (Sandbox Code Playgroud)
现在,编译器有可能生成第一种形式:
flag.store(true);
value = 42;
Run Code Online (Sandbox Code Playgroud)
现在,这不太好,不是吗?std::atomic 保证是“编译器屏障”,但在其他情况下,编译器很可能以类似的方式对内容进行洗牌。
| 归档时间: |
|
| 查看次数: |
2634 次 |
| 最近记录: |