c++ - 了解内存序列和 std::memory_order_relaxed
问题描述
我正在研究 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
is 3
,然后断言b
is 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
.
顺便说一句,官方术语是“sequenced before”和“sequenced after”
在现实世界中,硬件使事情变得更加复杂。操作可以在当前线程中以一种顺序出现,但另一个线程可以以另一种顺序看到它们,因此严格memory_order
的 s 必须采用额外的措施来确保跨线程的顺序一致。
严格来说,在这个例子中,如果使用memory_order_relaxed
我们将有未定义的行为,因为访问b
不是跨线程同步的。
推荐阅读
- python - 使用 Python 模拟车辆在网络上的轨迹
- matplotlib - matplotlib 以零为中心的堆积条形图
- sql - 替换表达式中的str
- android - 如何:NestedScrollView 角在滚动时消失
- swift - NSRegularExpression 拆分/重新解析匹配
- css - 搜索栏打开时如何摆脱填充
- azure - Azure AD B2C 在客户端凭据的 /oauth2/v2.0/token 端点中缺少随机数
- keycloak - 如何为应用程序仪表板应用 Keycloak css
- ffmpeg - ffmpeg 使用 tee muxer 重新连接输出错误
- dart - 重定向 HTTP/2 流连接路径目的地