memory_order_relaxed 在 C++ 并发操作中的代码中未按预期工作
memory_order_relaxed not work as expected in code from C++ concurrency in action
在问这个问题之前,根据this question的建议,我尝试将我的 VM 内核设置为大于 1(我将其设置为 2),此解决方案对作者有效,但对我无效。
C++ Concurrency in Action 一书在清单 5.5 中给出了一个例子
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed); // 1
y.store(true,std::memory_order_relaxed); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // 3
if(x.load(std::memory_order_relaxed)) // 4
++z;
}
int main()
{
for(int i = 0; i < 1000; i++){
// this loop is addeed by me in order to try 1000 times to see whether the assertion will fail
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0); // 5
}
}
书上说断言assert(z.load()!=0)
可能会失败。但是,我不能在测试时失败这个断言。
我在 64 位 Ubuntu(在 VMWare 下),g++ -g -o test --std=c++17 test.cpp -lpthread
,我在 gcc 5.4.0 和 gcc 7.2.0 下测试了程序。
我还通过 64 位 Windows 10(不在 VM 中)测试了该程序,Visual studio 2015.
同时在网上搜了一下,看到有人说这是x86架构的问题,不过我记得在x86下,Loads可能会随着旧店重新排序到不同的位置,所以我觉得可以有一个命令 2-3-4-1
可能会使此断言失败。
这本书是正确的,断言可能会失败,只是不是像你怀疑的那样在 x86 上。
明确地说,atomics make the specific guarantee that no data races will occur. It doesn't by itself guarantee absence of race conditions。
std::memory_order_relaxed
是所有内存顺序中最松散的。它是一切的底线。实际上,它 prevents compiler optimizations 围绕变量。
atomic<bool> x, y;
x.store(true,std::memory_order_relaxed);
y.store(true,std::memory_order_relaxed);
// is similar to...
volatile bool x, y;
x = true;
asm volatile("" ::: "memory"); // Tells compiler to stop optimizing here,
// but issues no instructions
y = true;
但是,CPU 本身可能决定重新排序写入,或者缓存系统可能决定稍后将写入发布到 x
。这最终导致断言失败。
就语言而言,这是因为写入与读取没有 happens before 关系,因此无法保证其结果。
最后,您在 x86 机器上看不到任何失败断言的原因是因为它本身就具有 acquire and release semantics 的读写能力。
// On x86, effectively
void write_x_then_y()
{
x.store(true,std::memory_order_release); // 1
y.store(true,std::memory_order_release); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_acquire)); // 3
if(x.load(std::memory_order_acquire)) // 4
++z;
}
也就是说,如果y
在// 3
被确定为true
,//2
之前发生的事情保证在[=16=之后可见].
在问这个问题之前,根据this question的建议,我尝试将我的 VM 内核设置为大于 1(我将其设置为 2),此解决方案对作者有效,但对我无效。
C++ Concurrency in Action 一书在清单 5.5 中给出了一个例子
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed); // 1
y.store(true,std::memory_order_relaxed); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // 3
if(x.load(std::memory_order_relaxed)) // 4
++z;
}
int main()
{
for(int i = 0; i < 1000; i++){
// this loop is addeed by me in order to try 1000 times to see whether the assertion will fail
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0); // 5
}
}
书上说断言assert(z.load()!=0)
可能会失败。但是,我不能在测试时失败这个断言。
我在 64 位 Ubuntu(在 VMWare 下),g++ -g -o test --std=c++17 test.cpp -lpthread
,我在 gcc 5.4.0 和 gcc 7.2.0 下测试了程序。
我还通过 64 位 Windows 10(不在 VM 中)测试了该程序,Visual studio 2015.
同时在网上搜了一下,看到有人说这是x86架构的问题,不过我记得在x86下,Loads可能会随着旧店重新排序到不同的位置,所以我觉得可以有一个命令 2-3-4-1
可能会使此断言失败。
这本书是正确的,断言可能会失败,只是不是像你怀疑的那样在 x86 上。
明确地说,atomics make the specific guarantee that no data races will occur. It doesn't by itself guarantee absence of race conditions。
std::memory_order_relaxed
是所有内存顺序中最松散的。它是一切的底线。实际上,它 prevents compiler optimizations 围绕变量。
atomic<bool> x, y;
x.store(true,std::memory_order_relaxed);
y.store(true,std::memory_order_relaxed);
// is similar to...
volatile bool x, y;
x = true;
asm volatile("" ::: "memory"); // Tells compiler to stop optimizing here,
// but issues no instructions
y = true;
但是,CPU 本身可能决定重新排序写入,或者缓存系统可能决定稍后将写入发布到 x
。这最终导致断言失败。
就语言而言,这是因为写入与读取没有 happens before 关系,因此无法保证其结果。
最后,您在 x86 机器上看不到任何失败断言的原因是因为它本身就具有 acquire and release semantics 的读写能力。
// On x86, effectively
void write_x_then_y()
{
x.store(true,std::memory_order_release); // 1
y.store(true,std::memory_order_release); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_acquire)); // 3
if(x.load(std::memory_order_acquire)) // 4
++z;
}
也就是说,如果y
在// 3
被确定为true
,//2
之前发生的事情保证在[=16=之后可见].