互斥锁如何确保变量的值在核心之间保持一致?

Ben*_*ers 20 c++ multithreading mutex

如果我有一个我要从一个线程写入并从另一个线程读取的int,我需要使用std::atomic,以确保它的值在内核之间是一致的,无论是否读取和写入它的指令在概念上是原子的.如果不这样做,可能是读取内核的缓存中有旧值,并且不会看到新值.这对我来说很有意义.

如果我有一些无法以原子方式读/写的复杂数据类型,我需要使用一些同步原语来保护对它的访问,例如std::mutex.这将防止对象进入(或被读取)不一致的状态.这对我来说很有意义.

对我来说没有意义的是互斥体如何帮助解决原子解决的缓存问题.它们似乎只是为了防止对某些资源的并发访问,而不是将该资源中包含的任何值传播到其他核心的缓存.是否有一部分他的语义我错过了处理这个?

sya*_*yam 6

内存屏障确保了内核之间的一致性(这也可以防止指令重新排序).使用时std::atomic,不仅会以原子方式访问数据,而且编译器(和库)也会插入相关的内存屏障.

互斥体以相同的方式工作:互斥体实现(例如,pthreads或WinAPI或者不是)在内部也插入内存屏障.

  • @GuyGreer:在我看来,它应该是一个单独的问题,但我甚至不确定它是否适合SO,因为主题是如此广泛(典型的"我们需要整本书来回答").在线搜索确实是获得基础知识的良好开端,然后可以提出*精确*问题. (3认同)

Tim*_*imo 5

大多数现代多核处理器(包括 x86 和 x64)都是缓存一致的。如果两个内核在缓存中拥有相同的内存位置,并且其中一个更新了该值,则更改会自动传播到其他内核的缓存。它效率低下(从两个内核同时写入相同的缓存线真的很慢)但没有缓存一致性,编写多线程软件将非常困难。

就像 syam 所说的,内存屏障也是必需的。它们阻止编译器或处理器重新排序内存访问,并强制写入内存(或至少写入缓存),例如,当由于编译器优化而将变量保存在寄存器中时。


Mik*_*ine 5

对此的正确答案是神奇的小精灵 - 例如It Just Works.每个平台的std :: atomic实现必须做正确的事情.

正确的是3个部分的组合.

首先,编译器需要知道它不能跨越边界移动指令[事实上它可以在某些情况下,但假设它没有].

其次,缓存/内存子系统需要知道 - 这通常是使用内存屏障完成的,虽然x86/x64通常具有如此强大的内存保证,在绝大多数情况下这不是必需的(这是一个很大的耻辱,因为它很好错误的代码实际上出错了).

最后,CPU需要知道它不能重新排序指令.现代CPU在重新排序操作方面非常积极,并确保在单线程情况下这是不明显的.他们可能需要更多提示,这在某些地方不会发生.

对于大多数CPU来说,第2部分和第3部分归结为同样的事情 - 内存屏障意味着两者兼而有之.第1部分完全在编译器内部,并且由编译器编写者来完成.

请参阅Herb Sutters谈论'原子武器'以获取更多有趣的信息.