【问题标题】:How does a mutex ensure a variable's value is consistent across cores?互斥锁如何确保变量的值在内核之间保持一致?
【发布时间】:2013-06-11 14:25:31
【问题描述】:

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

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

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

【问题讨论】:

  • 我没有得到你没有得到的东西。原子具有语义 [lock global mutex;做操作;解锁全局互斥体]。只有一些具有内在支持,因此可以隐式且快速地完成。
  • 仅供参考 - std::atomic 适用于所有数据类型。有趣的是,如果它太复杂,它就不会无锁 - 在这种情况下它会退回到锁。请参阅成员函数“is_lock_free()”。但是,如果你这样做而不是锁定,你仍然需要小心。
  • Balog,我了解互斥锁的语义,我了解原子如何执行它们的工作,并且我了解在 C++11 中它们正式具有额外的语义,可以防止不同的内核将旧值保留在缓存中.我不明白的是互斥锁是如何做到的。它们阻止对事物的并发访问,但我没有读过任何说明事物在不同内核的缓存中具有一致值的内容。

标签: c++ multithreading mutex


【解决方案1】:

对此的正确答案是魔法小精灵 - 例如。它只是工作。每个平台的 std::atomic 实现必须做正确的事情。

正确的事情是三个部分的组合。

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

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

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

对于大多数 CPU,第 2 部分和第 3 部分归结为同一件事 - 内存屏障意味着两者。第 1 部分完全在编译器内部,由编译器编写者来解决。

查看 Herb Sutters 的谈话 'Atomic Weapons' 了解更多有趣的信息。

【讨论】:

  • 这似乎是最完整的答案,谢谢!不幸的是,现在这意味着我需要问后续问题“内存屏障如何强制一致性”,因为我看不到阻止重新排序如何确保 CPU/内核确保它们缓存的值是同步的......他们只是当他们遇到障碍时冲洗一切?也许只是事物访问了屏障周围的一些指令?我想答案又是“魔法小精灵”:)
  • 另外,你好迈克!我想我加入后我们在 Rare 呆了一段时间!
  • @BenHymers 缓存始终同步。没有特殊的指令可以使缓存保持同步。它始终自动完成。
【解决方案2】:

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

互斥锁的工作方式相同:互斥锁实现(例如 pthreads 或 WinAPI 等)在内部也会插入内存屏障。

【讨论】:

  • 究竟什么是内存屏障?它是如何工作的?或者这应该是一个单独的问题?
  • 那应该是谷歌搜索 ;)
  • @GuyGreer:在我看来,这应该是一个单独的问题,但我什至不确定它是否适合 SO,因为该主题是如此广泛(典型的“我们需要一整本书来回答这个问题”)。在线搜索确实是了解基础知识的良好开端,然后可以提出精确的问题。
  • @syam 很公平,我认为答案中添加的链接是一个很好的折衷方案
  • @GuyGreer:是的,我试图写一个简短的解释,但没有用。德鲁添加维基百科链接比我想出的要好得多。
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 2019-11-01
    • 2015-05-03
    • 2012-05-25
    • 1970-01-01
    • 2013-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多