我有一些简单(希望)的问题,我一直无法找到答案 -
假设我有多个线程可以访问的对象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上看到任何关于每个写入易失性的文章或评论。但是,我知道写入是原子的。
编辑:如果我的第一个问题的答案是“b”不被视为易失性,而我的第二个问题的答案是写入确实是不稳定的,那么后续是,何时是interlocked.exhange有用,如果我们不关心'a'的先前值?
答案 0 :(得分:7)
传递给Exchange
的变量(或传递给任何方法的任何volatile变量)在传递时不会保留“volatile”......实际上它不需要volatile
(持续时间)方法调用)因为volatile
做的唯一事情是确保编译器不优化变量的使用(这通常意味着优化写入寄存器,因此值只能被“看到”由单个处理器)。在x86 / x64以外的处理器上,这有时意味着保证获取或释放语义的指令。 .NET不使用寄存器进行参数传递,因此volatile不会影响传递的参数的“波动性”。由于内存模型的可见性保证,它必须始终从内存中获取最新值
RE问题2:引用“有点”是真的,取决于字段的声明,有可见性保证w.r.t.领域;但没有“volatile”字段访问可以在某些使用阶段优化到寄存器中,可能隐藏其他处理器的某些写入。
Interlocked
交换使非原子操作看起来像原子。本质上的交换类似于:
var x = someVariable;
someVariable = y;
无论someVariable
的类型如何,这都不可能是原子的。 Exchange
使此操作成为原子。这也是非原子类型的原子,如double
,long
(32位)等。
使Exchange
做部分原子的一部分是使用内存栅栏 - 这使得写入可见而不是在内存栅栏后的指令序列中读取相同内存地址时重新排序。 / p>
如果您不关心“a”的先前值,为什么要使用Exchange
?如果您不关心实际的“交换”,那么VolatileWrite
似乎更合适。
或者,如果不需要“exchange”,您可以编写线程安全代码来模拟“A = B”,如下所示:
Thread.MemoryBarrier();
A=B;
FWIW,Interlocked
部分模拟了某些处理器中的比较和交换(CAS)指令。这些指令允许您在一条指令中执行这两项操作(使其成为原子)。如果没有像Interlocked
这样的东西,编译器可能很难推断出应该使用这些CAS指令之一。此外,Interlocked
在不支持这些CAS指令的处理器上提供原子使用(以及其他可能非原子指令,如inc和dec - 可能并非在所有处理器上都可用)
答案 1 :(得分:1)
如果'b'不是易变的,这个操作会不会这样对待?
当b
是共享变量时,我认为你不应该使用它。这样就消除了整个问题。但Exchange总会使用Memorybarrier,所以答案可能是肯定的。
何时,如果我们不关心'a'的前一个值,是否有互锁有效。
double
的重载非常有用,因为对double
的写入不是原子的。对于32位系统上的Int64也是如此。
但是对于原子类型的Exchange()
重载,用例不太清楚。我认为大多数算法都支持CompareExcange()
。
因此,请将其视为原子Write()。
答案 2 :(得分:1)
如果'b'不是易变的,这个操作会不会这样对待它?
是的,因为根据this source,Interlocked
类的所有方法都会生成隐式内存栅栏。