【问题标题】:How does the lock statement ensure intra processor synchronization?lock 语句如何确保处理器内同步?
【发布时间】:2011-08-25 04:18:20
【问题描述】:

我有一个同时执行两个线程的小型测试应用程序。一个增加static long _value,另一个减少它。我已通过 ProcessThread.ProcessorAffinity 确保线程与不同的物理(无 HT)内核相关联以强制处理器内部通信,并且我已确保它们在执行时间上重叠很长时间。

当然,以下不会导致零:

for (long i = 0; i < 10000000; i++)
{
    _value += offset;
}

因此,合乎逻辑的结论是:

for (long i = 0; i < 10000000; i++)
{
    Interlocked.Add(ref _value, offset);
}

这当然会导致零。

但是,以下也导致零:

for (long i = 0; i < 10000000; i++)
{
    lock (_syncRoot)
    {
        _value += offset;
    }
}

当然,lock 语句确保读取和写入不会重新排序,因为它使用了完整的栅栏。但是,我找不到有关处理器缓存同步的任何信息。如果没有任何缓存同步,我想我应该在两个线程完成后看到与 0 的偏差?

谁能向我解释一下lock/Monitor.Enter/Exit 如何确保处理器缓存(L1/L2 缓存)同步?

【问题讨论】:

  • “我已确保它们同时开始”。不完全是,您无法控制操作系统相对于其他应用程序如何调度您的应用程序的线程。但在大多数情况下,这无关紧要。
  • 抱歉,表述错误。我已确保它们大致在同一时间开始,并且我确保它们在执行时间上重叠。更新了问题。感谢您的观察。

标签: c# .net multithreading synchronization


【解决方案1】:

这种情况下的缓存一致性不依赖于lock。如果您使用lock 语句,它可以确保您的汇编程序命令没有混合。 a += b 不是处理器的原子,它看起来像:

  • 将数据从内存加载到寄存器中
  • 增量数据
  • 将数据存储回来

如果没有锁,它可能是:

  • 将数据从内存加载到寄存器 X
  • 将数据从内存加载到寄存器 Y
  • 增量数据(在 X 中)
  • 递减数据(以 Y 为单位)
  • 存储回数据(来自 X)
  • 存储数据(从 Y)//在这种情况下增量丢失。

但这与缓存一致性无关,它是一个更高级的功能。

因此,lock 不能确保缓存是同步的。高速缓存同步是处理器内部功能,不依赖于代码。你可以阅读它here

当一个核心将一个值写入内存,然后当第二个核心尝试读取该值时,它的缓存中不会有实际的副本,除非它的缓存条目无效,从而发生缓存未命中。并且此缓存未命中会强制将缓存条目更新为实际值。

【讨论】:

  • 请注意,并非所有处理器(例如 Blackfin)都会在硬件中自动缓存一致性。但可能所有具有 .net 实现的处理器都可以......
  • 您是说缓存一致性本身不是您在软件中遇到过的问题吗?
  • 您可以使用代码中的缓存进行操作(例如预取或清除命令),但据我所知,您无法使用代码中与缓存一致性相关的内容进行操作。
  • 酷。谢谢你的回答。
  • 为了解决这个问题,Intel/AMD 正在尝试取消缓存一致性,因为它太啰嗦并且成为许多核心 CPU 的一个大问题。每次 CPU 更改地址时,它都必须向所有其他内核广播以将任何相关的缓存行标记为脏。此外,根据我的阅读,“原子”更改在 ARM 多核系统上是昂贵的,因为没有硬件缓存一致性。所以 lock/Interlocked 是 Win8 ARM 多核系统的昂贵调用。我不记得我在哪里读到的,但我觉得它很有趣。这也使 ARM 的晶体管和电源效率更高,工作量更少。
【解决方案2】:

CLR 内存模型guarantees (requires) that loads/stores can't cross a fence。由 CLR 实现者在真实硬件上强制执行此操作,which they do。但是,这是基于所宣传/理解的硬件行为,canwrong

【讨论】:

    【解决方案3】:

    lock 关键字只是一对System.Threading.Monitor.Enter()System.Threading.Monitor.Exit() 调用的语法糖。 Monitor.Enter()Monitor.Exit() 的实现建立了一个内存栅栏,需要执行架构适当的缓存刷新。因此,您的其他线程将不会继续,直到它可以看到由执行锁定部分产生的存储。

    【讨论】:

    • 但是什么进程使存储对另一个线程可见?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-14
    • 2023-01-19
    • 2015-07-03
    • 2021-10-03
    • 1970-01-01
    • 2016-02-09
    相关资源
    最近更新 更多