【问题标题】:Interlocked.Exchange clarificationInterlocked.Exchange 澄清
【发布时间】:2012-10-22 14:27:55
【问题描述】:

我有几个简单的(希望如此)问题我一直无法找到答案 -

假设我有多个线程可以访问的对象 a、b。

Interlocked.Exchange(ref a, b)

如果'b'不是易失性的,这个操作会这样对待它吗?即它会从内存中获取该变量的最新值吗?如果是这样,那是写的“原子”读吗?我知道 Interlocked.Exchange 的主要目的是您将获得以前的“a”值作为新写入的原子操作。但我的主要困惑是“b”的实际值被写入“a”。

我的第二个问题与本文中的一句话有关:

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

“有趣的一点是,根据此处和此处记录的内存模型,C# 中的所有写入都是易失性的,并且可能也是这样实现的。C# 语言的 ECMA 规范实际上定义了一个较弱的模型,其中写入不是默认为 volatile。”

这是真的吗?如果是这样,如果不关心“a”的先前值,是否有 Interlocked.Exchange 的目的? (关于我的第一个例子)。我在 StackOverflow 上没有看到任何其他文章或 cmets 围绕每次写入都是易失性的。但是,我知道写入是原子的。

编辑:如果我的第一个问题的答案是 'b' 不被视为易失性,而我的第二个问题的答案是写入确实是易失性的,那么跟进的是,何时 interlocked.exhange 有用,如果我们不关心'a'的先前值吗?

【问题讨论】:

  • b 的“波动性”应该无关紧要。

标签: c# multithreading .net-4.0 interlocked


【解决方案1】:

传递给Exchange的变量(或传递给任何方法的任何易失性变量)在传递时不会保留“波动性”...实际上不需要它是volatile(在方法调用期间) 因为volatile 所做的唯一事情是确保编译器不会优化变量的使用(这通常意味着优化写入寄存器,因此该值只能被单个处理器“看到”)。在 x86/x64 以外的处理器上,这有时意味着保证获取或释放语义的指令。 .NET 不使用寄存器来传递参数,因此 volatile 不会影响传递的参数的“波动性”。由于内存模型的可见性保证,它必须始终从内存中获取最新值

RE 问题 2:引用是“有点”真实的,取决于字段的声明,有可见性保证 w.r.t.字段;但是如果没有“易失性”字段访问,则可以在某些使用阶段将其优化为寄存器,从而可能隐藏其他处理器的某些写入。

Interlocked 交换使非原子操作看起来是原子的。交换的性质类似于:

var x = someVariable;
someVariable = y;

无论someVariable 的类型如何,它都不能是原子的。 Exchange 使这个操作原子化。这对于非原子类型也是原子的,例如 doublelong(32 位)等。

Exchange 使这个原子化的部分工作是使用内存栅栏——这使得写入可见,并且不会随着内存栅栏之后指令序列中相同内存地址的读取而重新排序。

如果您不关心“a”的先前值,为什么还要使用Exchange?如果您不关心实际的“交换”,那么VolatileWrite 似乎更合适。

或者,如果不需要“交换”,您可以编写线程安全代码来建模“A=B”,如下所示:

Thread.MemoryBarrier();
A=B;

FWIW,Interlocked 部分围绕某些处理器中的比较和交换 (CAS) 指令建模。这些指令允许您在一条指令中执行这两个操作(使其成为原子)。如果没有Interlocked 之类的东西,编译器可能很难推断出应该使用这些 CAS 指令之一。此外,Interlocked 在不支持这些 CAS 指令(以及其他潜在的非原子指令,如 inc 和 dec,可能并非在所有处理器上都可用)的处理器上提供原子使用。

【讨论】:

  • 感谢您的回复。 w.r.t 你的问题是,如果你不关心 a 的先前值,为什么要使用交换 - 如果 B 不是易失性的,但我们想确保我们没有将 A 设置为 B 的缓存值怎么办?如果问题 2 只是“有点”正确,那么以这种方式设置 A 与仅设置 A = B 是否可能不同?
  • 如果你不关心之前的值,Volatile.Write 可能更合适——尽管我不认为它支持与Exchange 相同的所有类型。使用VolatileWriteInterlocked.Exchange(或lockvolatile)向其他线程可见的字段写入值总是更安全。
【解决方案2】:

如果 'b' 不是 volatile,这个操作会这样对待它吗?

是的,因为根据this sourceInterlocked类的所有方法都会产生隐式内存栅栏。

【讨论】:

  • 谢谢。这是否也意味着读取 B 的值是原子的,并将该值写入 A?或者在分配给 A 之前,B 的值在技术上仍然可以更改吗?
  • @user981225:嗯b是按值传递给方法的,所以方法调用后的任何变化都不会干扰方法接收到的值。
【解决方案3】:

如果 'b' 不是 volatile,这个操作会这样对待它吗?

b 是一个共享变量时,我认为你不应该使用它。这样就消除了整个问题。但 Exchange 将始终使用 Memorybarrier,因此答案可能是肯定的。

如果我们不关心 'a' 的先前值,interlocked.exhange 什么时候有用?

double 的重载非常有用,因为对double 的写入不是原子的。 32 位系统上的 Int64 也是如此。

但是对于原子类型的Exchange() 重载,用例不太清楚。我认为大多数算法都会支持 CompareExcange()

所以认为它是原子的 Write()。

【讨论】:

  • 谢谢。您能否解释一下为什么当 b 是共享变量时您认为不应该使用它?
  • 扭转局面:何时/为什么将共享 B 写入共享 A?
  • 这样说才有意义 :)
猜你喜欢
  • 2019-11-15
  • 2013-06-05
  • 2013-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-13
  • 2020-07-30
  • 2020-04-02
相关资源
最近更新 更多