我正在研究C++内存序列,但这非常令人困惑。
例如:
void sumUp(std::atomic<int>& sum, std::vector<int>& val)
{
int tmpSum = 0;
for(auto i = 0; i < 100; ++i) tmpSum += val[i];
sum.fetch_add(tmpSum, std::memory_order_relaxed);
}
我不知道
sum.fetch_add()
在tmpSum += val[i]
之后运行。由于顺序不正确,
sum.fetch_add()
可以在tmpSum += val[i]
之前运行吗?那么总和 0 可能吗?
非常感谢。
最佳答案
memory_order
在单个线程的上下文中没有可观察到的效果:
让我们看看(x
,a
和b
最初是0
):
auto t1(std::atomic<int>& x, int& a, int& b)
{
a = 3; // 1
b = 5; // 2
x.store(a, std::memory_order_relaxed); // 3
}
因为(1)和(2)彼此不依赖,所以编译器可以对其重新排序。例如。可以做(1)->(2)或(2)->(1)
由于(3)依赖于(1)(((1)写入
a
而(3)从a
读取)),因此编译器无法在(1)之前执行(3)。这与(3)中指定的存储顺序无关因为(3)不依赖于(2),所以通常在单线程模型中,编译器可以在(2)之前执行(3)。
但是由于
x
是原子的,因此请考虑另一个线程执行此操作(x
,a
和b
是对t1
提供的相同参数的引用,并且最初都是0
):auto t2(std::atomic<int>& x, int& a, int& b)
{
while(x.load(std::memory_order_relaxed) == 3) // 4
assert(b == 5); // 5
}
该线程等待,直到
x
是3
,然后断言b
是5
。现在,您可以看到如何在顺序单线程世界(2)和(3)中重新排序而没有任何可观察到的行为,但是在多线程模型中,(2)和(3)的顺序可能会对行为产生影响该程序。这就是
memory_order
的作用:它指定是否可以在原子之前或之后对单个线程没有任何影响的情况下重新排序的操作按原样进行重新排序。原因是它们可能会对多线程程序产生影响。编译器只能知道程序员,因此无法知道,因此无法知道额外的memory_order
参数。使用
memory_order_relaxed
时,断言可能会失败,因为(2)可能会在(3)之后发生,但是使用memory_order_seq_cst
(默认值)时,断言将永远不会失败,因为(2)
会在(3)之前发生。回到您的示例,无论您指定哪种
memory_order
,都可以保证tmpSum += val[i];
将在sum.fetch_add(tmpSum, std::memory_order_relaxed);
之前发生,因为第二个依赖于第一个。 memory_order
将影响不影响原子操作的指令的可能重新排序。例如。如果您有一个int unrelated = 24
。顺便说一句,官方术语是“之前排序”和“之后排序”
在现实世界中,硬件使事情变得更加复杂。操作可以在当前线程中以一种顺序出现,但是另一线程可以以另一种顺序看到它们,因此更严格的
memory_order
必须采取额外的措施来确保线程之间的顺序一致。严格地说,在此示例中,如果使用
memory_order_relaxed
,我们将具有“未定义行为”,因为对b
的访问未跨线程同步。关于c++ - 了解内存序列和std::memory_order_relaxed,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52719079/