【问题标题】:Multi-Threading on multi core architecture多核架构上的多线程
【发布时间】:2011-03-09 03:18:29
【问题描述】:

当您遇到线程 A 读取某个全局变量而线程 B 写入同一个变量的情况时,现在除非读/写在单个内核上不是原子的,您可以在不同步的情况下执行此操作,但是在运行时会发生什么多核机器?

【问题讨论】:

  • “除非读/写在单核上不是原子的,否则无需同步就可以做到”是什么意思?三重否定让我大吃一惊。是否应该是“虽然读/写不是原子的,但在单个核心上你可以在不同步的情况下做到这一点”? (这是错误的,顺便说一句)你能澄清你的意思吗?
  • "除非读/写不是原子的" -> "如果读/写是原子的"。
  • @jalf:我的意思是,如果操作是原子的,您可以在单个核心上进行读取或写入,而无需使用同步。然而,根据下面给出的答案,这是一个糟糕的假设。

标签: c++ windows multithreading multicore


【解决方案1】:

即使在单核上,您也不能假设操作是原子的。这可能是您在汇编程序中编码的情况,但是,如果您根据您的问题使用 C++ 编码,您不知道它将编译成什么。

您应该在编码的抽象级别依赖同步原语。在您的情况下,这就是 C++ 的线程调用。无论它们是 pthreads、Windows 线程还是完全其他的东西。

这与我在另一个关于whether i++ was thread-safe 的答案中给出的推理相同。底线是,你不知道,因为你没有编码到那个级别(如果你正在做内联汇编和/或你理解并且可以控制幕后发生的事情,你就不再编码了C++ 级别,你可以忽略我的建议)。

操作系统和/或操作系统类型的库对它们运行的​​环境了解很多,远比 C++ 编译器了解的多。使用适当的同步原语将为您省去很多烦恼。

【讨论】:

  • +1,真的很好。请注意,当您在不同的抽象级别进行编码时,可能会发生非常奇怪的事情。编译器甚至可能一开始就决定您不想将该变量写入内存! - IE。它可以将其缓存在寄存器中,并且永远不会生成将变量写回主内存的指令,以便其他线程可以看到。
  • 当然,即使你在做inline asm,你仍然不能确定同步,因为CPU reordering指令。
  • @jalf,当然可以,只需将每秒指令设为序列化 CPUID 并挂起性能影响 :-)
【解决方案2】:

它会遇到与单核相同的缺陷,但由于必须在内核之间进行 L1 缓存同步,因此会产生额外的延迟。

注意 - “你可以在不同步的情况下做到这一点”并不总是正确的说法。

【讨论】:

  • +1 指出即使使用单核机器也是不安全的。
【解决方案3】:

即使在单核机器上,也绝对不能保证在没有显式同步的情况下也能正常工作。

这有几个原因:

  • 操作系统可能在任何时间(任意两条指令之间)中断一个线程,然后运行另一个线程,并且
  • 如果没有显式同步,编译器可能会非常随意地重新排序指令,从而破坏您认为的任何保证,并且
  • 甚至 CPU 也可以执行相同的操作,即时重新排序指令。

如果你想在两个线程之间进行正确的通信,你需要某种同步。 总是没有例外。

这种同步可能是操作系统或线程 API 提供的互斥锁,也可能是特定于 CPU 的原子指令,或者只是一个普通的内存屏障。

【讨论】:

    【解决方案4】:

    对于多核机器上的非原子操作,您需要使用系统提供的 Mutex 来同步访问。

    对于 C++,boost mutex 库提供了几种互斥类型,为操作系统提供的互斥类型提供一致的接口。

    如果您选择将 boost 作为您的同步/多线程库,您应该阅读Synchronization 概念。

    【讨论】:

    • 所以操作系统不会在 CPU 内核之间同步?
    • 不,同步不是自动的。
    【解决方案5】:

    根据您的情况,以下可能是相关的。虽然它不会使您的程序运行不正确,但它可以在速度上产生很大的不同。即使您没有访问相同的内存位置,如果两个内核在缓存中的同一页面上颠簸(尽管不是同一位置,因为您仔细同步了数据结构),您也可能会因缓存效应而受到性能影响。

    这里对“虚假共享”有一个很好的概述: http://www.drdobbs.com/go-parallel/article/showArticle.jhtml;jsessionid=LIHTU4QIPKADTQE1GHRSKH4ATMY32JVN?articleID=217500206

    【讨论】:

      【解决方案6】:

      就(新的)C++ 标准而言,如果程序包含数据竞争,则程序的行为是未定义的。如果存在线程交错,则程序存在数据竞争,从而它包含来自不同线程的两个相邻的冲突内存访问(这只是一种非常正式的说法,即“如果两个冲突的访问可以同时发生,则程序存在数据竞争” )。

      请注意,无论您在多少个内核上运行,程序的行为都是未定义的(特别是优化器可以根据需要重新排序指令)。

      【讨论】:

        【解决方案7】:

        没有人提到隐式同步的优缺点。

        主要的“优点”当然是程序员可以编写任何东西而不必为同步而烦恼。

        主要的“缺点”是这需要很多时间。隐式同步需要通过缓存逐渐下降到至少(您可能会认为)两个内核共有的第一个缓存。错误的!计算机中可能安装了多个物理处理器,因此同步不能在缓存处停止,它需要一直到 RAM。如果你想在那里同步,你还需要与其他需要与内存同步的设备同步,即任何总线主控设备。总线主控设备可能是经典 PCI 总线上的卡,并且可能以 33 MHz 运行,因此隐式同步也需要等待它们确认可以写入或读取特定 RAM 位置。我们说的是内核和最慢总线之间的时钟速度差异只有 100 倍,而最慢的总线需要几个自己的总线周期才能以可靠的方式做出反应。因为同步必须是可靠的,否则它是没有用的。

        因此,在实现电子设备以实现隐式同步(最好留给程序员明确处理)和可以在必要时同步的更快系统之间进行选择,答案是显而易见的。

        同步的显式键是 LOCK 前缀和 XCHG mem,reg 指令。

        你可以说隐式同步就像训练轮:你不会摔倒在地,但你不能走得特别快或转得特别快。很快你就会厌倦并想要继续做真正的事情。当然,你会受伤,但在这个过程中,你要么学习,要么放弃。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-03-09
          • 1970-01-01
          • 2023-04-02
          • 2020-02-26
          • 1970-01-01
          • 1970-01-01
          • 2016-08-29
          • 1970-01-01
          相关资源
          最近更新 更多