【问题标题】:Is a memory barrier needed (.net x86 or x64) when dereferencing fields?取消引用字段时是否需要内存屏障(.net x86 或 x64)?
【发布时间】:2011-06-09 11:32:29
【问题描述】:

在如下代码中,如果 Proc1 和 Proc2 在不同的处理器上同时执行,ThingVal2 是否有可能获得 5 以外的值(例如零)?

类 SimpleThing 公共 X 作为整数 Sub New(ByVal value As Integer) X = 价值 结束子 结束类 类并发测试 Dim Thing1 作为新的 SimpleThing(5) Dim Thing2 作为新的 SimpleThing(0) 将 ThingRef 调暗为 SimpleThing = Thing1 将 ThingVal1、ThingVal2 调暗为整数 子程序1() 事物2.X = 5 Threading.Thread.MemoryBarrier() ThingRef = Thing2 结束子 子进程2() ThingVal1 = Thing2.X ThingVal2 = ThingRef.X 结束子 结束类

我知道在像 IA64 这样的弱模型中,Proc2 很有可能会看到 ThingRef 发生了变化,但看不到 Thing2 的字段 X 发生了变化。在 x86 或 x64 上运行的 .Net 应用程序是否存在这种风险?如果 Proc1 创建了一个 SimpleThing 的新实例,将其 X 字段设置为 5,然后将 ThingRef 设置为指向它,这是否足以避免危险,或者是否有可能将新事物分配在缓存行上与 Proc2 线程访问过的其他内容共享?

多线程代码的一个常见范例是构造一个不可变对象并设置一个可变引用来指向它(可能使用 Interlocked.CompareExchange)。在 x86/x64 下读取不可变类型而不考虑线程是否总是安全的,还是会导致问题?如果是后者,在 vb.net 中保证可靠行为的首选方式是什么?

另外,是否有任何方法可以指定代码必须以不会发生此类问题的方式运行(例如,在无法保证正确运行的 IA64 等设备上将执行限制为单个内核)?

【问题讨论】:

  • 只有在一个周期内可以读取值时才能保证不可变。 Int 和 bool 属于该类别。不太确定 long,绝对不是字符串。
  • @SRM:不可变对象是一个一旦引用可供其他任何人使用就永远不会改变的对象。上面的“SimpleThing”对象不是不可变的,因为我试图准确地询问在 .net x86 和 .net x64 内存模型下什么是保证和不保证。

标签: vb.net x86 64-bit memory-model memory-barriers


【解决方案1】:

好的,你提出了很多问题。我会尽量回答我所知道的。

-1。广告您的代码示例:

CLR 自 2.0 起已订购商店。这意味着您的 ThingVal 在 x86/x64 上将始终为 5。当然。我没有在真正的 IA64 上尝试过,但它应该在 IA64 上也能正常工作,因为 CLR 应该确保在所有平台上进行有序写入,并且对于您的简单示例来说应该足够了。

-2。广告 IA64 与 x86/x64:

x86/x64 具有不同的内存语义,并且没有像 IA64 那样的风险。这里唯一可能的问题是您实际上使用的是更高级别的语言,如果它使用优化编译器(如 C++),您无法在不确切知道编译器如何进行优化的情况下预测任何内容。未记录:VB 不执行任何全局优化等,因此您的代码应该是安全的。

-3。 ad 不可变的:

如果你真的只是阅读它并且它真的是不可变的,那么它是安全的。

-4。广告单核:

您可以设置线程亲和性。它是每个线程的标准属性,并定义线程可以在哪些 CPU 上运行。 (.net的线程亲和性设置直接改变了操作系统中的亲和性。)但是它会让你的程序运行缓慢。

另外,您可以切换到 C# 并使用 volatile 关键字。它将帮助您更轻松地生活,因为它会对所有 CPU 立即看到的 volatile 变量进行任何更改,这反过来将解决您在此处提出的所有可能的问题。不幸的是,VB 没有提供这个关键字。

【讨论】:

  • 我知道 x86 已经订购了商店,但这里的问题是负载依赖性。在写入 Instance2.X 或 ThingRef 之前,可能已经在 Proc2 中读取了来自 instance2 的字段 X。在 .net 下,是否可以保证当代码读取 ThingRef.X 时,它不会简单地使用读取 Instance2.X 的缓存值?这是一个我没有听说过的极端案例。
  • 我知道可以指定某些线程只应该运行在例如核心#5,但这似乎过于具体。真正需要的是一种指定两个或多个线程不得同时在不同处理器上运行的方法,并且任何时候执行从一个处理器移动到另一个处理器时,旧处理器和新处理器都必须按顺序刷新它们的缓存。
  • 是的,“有序存储”确保当您阅读 ThingRef.X 时,您要么同时阅读新 ThingRef 和新 X,要么阅读旧 ThingRef 和旧 X。这是因为您的 X 在 ThingRef 内,所以你总是在 X 之前阅读 ThingRef。所以它是安全的。如果 X1 和 X2 在同一个对象中并且它们彼此相关,那么即使使用有序存储也可能不安全。
  • @Al Kepp:是否有任何明确证明对象实例中的字段保证在对象引用之后加载的内容,即使同一个实例字段刚刚通过另一个对象引用访问过?将通过什么方式执行该保证?完整的内存屏障很昂贵(将来可能会变得更昂贵),但我不知道如何指定更窄的约束。评估 foo.x + bar.y 最多应该需要一个屏障(fetch foo、fetch bar、barrier、fetch fetchedfoo.x 和 fetchedbar.y),但我不知道编译器可以做到这一点。
猜你喜欢
  • 2018-10-02
  • 2015-11-13
  • 2012-09-08
  • 2012-04-07
  • 2011-10-12
  • 2014-01-20
  • 1970-01-01
  • 2017-01-05
  • 2015-12-17
相关资源
最近更新 更多