c++ - 对 std::atomic 变量的更改(读/写)如何跨线程传播

标签 c++ caching c++11 memory-model

我最近问过这个问题do-i-need-to-use-memory-barriers-to-protect-a-shared-resource

对于这个问题,我得到了一个非常有趣的答案,它使用了这个假设:

Changes to std::atomic variables are guaranteed to propagate across threads.

为什么会这样?它是如何完成的?这种行为如何符合 MESI 协议(protocol)?

最佳答案

它们实际上不必传播,缓存一致性模型(MESI 或更高级的东西)为您提供内存行为一致的保证,几乎就像它是平坦的并且不存在缓存拷贝一样。顺序一致性增加了系统中所有代理对相同观察顺序的保证(注意 - 大多数 CPU 不单独通过 HW 提供顺序一致性)。

如果一个线程执行内存写入(甚至不是原子的),它运行的核心将获取该行并获得对其的所有权。一旦写入完成,任何试图观察该行的线程都保证看到更新的值,即使该行仍然驻留在修改核心中——通常这是通过监听核心并从中获取该行作为响应来实现的.高速缓存一致性协议(protocol)将保证,如果这样的修改存在于某个核心本地 - 寻找该行的任何其他核心最终一定会看到它。为此,CPU 可以使用监听过滤器、目录管理(通常用于跨套接字一致性)或其他方法。

现在,您要问为什么原子很重要?有两个原因。首先 - 以上所有内容仅适用于变量驻留在内存中而不是寄存器中的情况。这是编译器的决定,所以正确的类型告诉它这样做。其他范例(如 open-MP 或 POSIX 线程)有其他方式告诉编译器变量需要通过内存共享。 其次 - 现代内核乱序执行操作,我们不希望任何其他操作传递该写入并暴露陈旧数据。 std::atomic 告诉编译器强制执行最强的内存排序(通过使用显式防护或锁定 - 检查生成的汇编代码),这意味着来自所有线程的所有内存操作将具有相同的全局排序。如果您不这样做,就会发生奇怪的事情,例如核心 A 和核心 B 对同一位置的 2 次写入的顺序不一致(这意味着它们可能会在其中看到不同的最终值)。

当然,最后是实际的原子性——如果你的数据类型不是保证原子性的,或者它没有正确对齐——这也会为你解决这个问题(否则一致性问题会加剧——想想一些线程尝试更改 2 个缓存行之间的值拆分,以及看到部分值的不同内核)

关于c++ - 对 std::atomic 变量的更改(读/写)如何跨线程传播,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28121288/

相关文章:

c++ - 如何使用 clang 将我的 c++ 程序与 osx 上的 libstdc++ 静态链接?

grails - 如何在Grails中使域类缓存超时(即指定最长期限)

c++ - 我们如何在线程中使用参数

c++ - 将通用引用转换为可调用的 void 指针,反之亦然

c++ - 在 C 和 C++ 中发送 UDP 套接字

c++ - Gcc 找不到 wine 库

c++ - 如果声明了析构函数,为什么这段代码无法编译?

ruby-on-rails - 无法在 Rails 上进行 expire_action

caching - 异步数据缓存 API (.NET)

C++ 匿名 union 重新声明错误