引用赋值是原子的,为什么需要Interlocked.Exchange(ref Object,Object)?

时间:2010-02-03 13:23:44

标签: c# multithreading atomicity volatility

在我的多线程asmx web服务中,我有一个我自己的类型SystemData的类字段_allData,其中包含少量标记为List<T>的{​​{1}}和Dictionary<T>。系统数据(volatile)偶尔刷新一次,我通过创建另一个名为_allData的对象并用新数据填充它的数据结构来完成。一旦完成,我只需指定

newData

这应该有效,因为赋值是原子的,并且具有对旧数据的引用的线程继续使用它,其余的在分配之后具有新的系统数据。但是我的同事说我不应该使用private static volatile SystemData _allData public static bool LoadAllSystemData() { SystemData newData = new SystemData(); /* fill newData with up-to-date data*/ ... _allData = newData. } 关键字和简单的分配,而应该使用volatile因为他说在某些平台上不能保证引用赋值是原子的。此外:当我将InterLocked.Exchange字段声明为the _allData

volatile

产生警告“对volatile字段的引用不会被视为volatile”我应该怎么看待这个?

4 个答案:

答案 0 :(得分:164)

这里有很多问题。一次考虑一个:

  

引用赋值是原子的,为什么需要Interlocked.Exchange(ref Object,Object)?

参考分配是原子的。 Interlocked.Exchange不仅仅执行引用赋值。它读取变量的当前值,隐藏旧值,并将新值分配给变量,所有这些都作为原子操作。

  

我的同事说,在某些平台上,不能保证引用分配是原子的。我的同事是否正确?

没有。保证所有.NET平台上的引用分配都是原子的。

  

我的同事正在虚假场所推理。这是否意味着他们的结论不正确?

不一定。你的同事可能会因为不好的理由给你很好的建议。也许还有一些其他原因你应该使用Interlocked.Exchange。无锁编程非常困难,当您离开该领域专家所支持的成熟实践的那一刻,您就会处于杂草之中并冒着最恶劣的竞争条件。我既不是这方面的专家也不是你的代码专家,所以我不能以某种方式作出判断。

  

产生警告“对volatile字段的引用不会被视为volatile”我应该怎么看待这个?

你应该理解为什么这是一般的问题。这将导致理解为什么警告在这种特殊情况下不重要。

编译器发出此警告的原因是因为将字段标记为volatile意味着“此字段将在多个线程上更新 - 不生成任何缓存此字段值的代码,并确保任何读取或者这个字段的写入不会通过处理器缓存不一致“及时向前和向后移动”。“

(我假设你已经理解了所有这些。如果你没有详细了解volatile的含义以及它如何影响处理器缓存语义,那么你就不明白它是如何工作的,不应该使用volatile。 - 免费程序很难做到正确;确保你的程序是正确的,因为你了解它是如何工作的,而不是偶然的。)

现在假设您通过将ref传递给该字段来创建一个变量,该变量是volatile字段的别名。在被调用的方法中,编译器没有任何理由知道引用需要具有volatile语义!编译器会为无法实现volatile字段规则的方法快速生成代码,但变量 是一个volatile字段。这完全破坏了你的无锁逻辑;假设始终使用volatile语义始终访问volatile字段。有时将其视为易变性是没有意义的,而不是其他时间;你必须始终保持一致,否则你无法保证其他访问的一致性。

因此,编译器会在您执行此操作时发出警告,因为它可能会完全破坏您精心开发的无锁逻辑。

当然,Interlocked.Exchange 编写的,以期望一个volatile字段并做正确的事情。因此该警告具有误导性。我非常后悔;我们应该做的是实现一些机制,其中像Interlocked.Exchange这样的方法的作者可以在方法上放置一个属性,说“这个方法采用ref对变量强制执行volatile语义,因此禁止警告”。也许在编译器的未来版本中我们将这样做。

答案 1 :(得分:9)

你的同事是错的,或者他知道C#语言规范没有的东西。

5.5 Atomicity of variable references

  

“读写以下内容   数据类型是原子的:bool,char,   byte,sbyte,short,ushort,uint,int,   float和引用类型。“

因此,您可以写入易失性参考,而不会有获得损坏值的风险。

当然,您应该小心决定哪个线程应该获取新数据,以最大程度地降低一次多个线程执行此操作的风险。

答案 2 :(得分:6)

Interlocked.Exchange< T >

  

将指定类型T的变量设置为指定值,并返回原始值作为原子操作。

它改变并返回原始值,它没用,因为你只想改变它,正如Guffa所说,它已经是原子的。

除非将探查器证明是您应用程序的瓶颈,否则您应该考虑使用unsing锁,更容易理解并证明您的代码是正确的。

答案 3 :(得分:2)

Iterlocked.Exchange()不仅仅是原子的,它还负责内存可见性:

  

以下同步函数使用适当的障碍来确保内存排序:

     

进入或离开关键部分的功能

     

发出同步对象信号的函数

     

等待功能

     

互锁功能

Synchronization and Multiprocessor Issues

这意味着除了原子性之外,还可以确保:

  • 对于调用它的线程:
    • 未完成指令的重新排序(由编译器,运行时或硬件完成)。
  • 对于所有主题:
    • 在此指令之前没有对内存的读取将看到此指令所做的更改。
    • 此指令后的所有读取都将看到此指令所做的更改。
    • 在此指令更改到达主存储器之后,所有对该存储器的写操作都会发生(通过在完成时将该指令更改为主存储器并且不让硬件冲洗它拥有其开启时序)。