2010-02-03 74 views
82

在我的多线程asmx web服务中,我有一个我自己的类型SystemData的类字段_allData,它由几个List<T>Dictionary<T>组成,标记为volatile。系统数据(_allData)稍后刷新一次,我通过创建另一个名为newData的对象并使用新数据填充其数据结构。当它这样做我只是分配引用赋值是原子,所以为什么Interlocked.Exchange(ref Object,Object)需要?

private static volatile SystemData _allData 

public static bool LoadAllSystemData() 
{ 
    SystemData newData = new SystemData(); 
    /* fill newData with up-to-date data*/ 
    ... 
    _allData = newData. 
} 

这应该工作,因为任务是原子,并且具有参考旧数据线程继续使用它,只是分配后剩下的有新的系统数据。然而,我的同事说,而不是使用volatile关键字和简单的assigment我应该使用InterLocked.Exchange,因为他说,在某些平台上,不保证参考分配是原子。另外:当我宣布the _allData字段volatile

Interlocked.Exchange<SystemData>(ref _allData, newData); 

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

回答

141

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

引用分配是原子为什么Interlocked.Exchange(ref对象,对象)需要?

引用赋值是原子的。 Interlocked.Exchange不会仅执行引用分配。它读取变量的当前值,存储旧值,并将新值赋给变量,全部作为原子操作。

我的同事说在某些平台上不能保证参考指定是原子的。我的同事是否正确?

编号参考分配保证在所有.NET平台上都是原子的。

我的同事是从错误的前提推理。这是否意味着他们的结论是不正确的?

不一定。你的同事可能因为不好的原因给你提供很好的建议。也许你应该使用Interlocked.Exchange有其他一些原因。无锁编程是非常困难的,当你离开现场专家所熟知的行之有效的做法时,你就会脱离杂草并冒着最恶劣的竞争条件冒险。我既不是这个领域的专家,也不是你的代码的专家,所以我不能以这种或那种方式做出判断。

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

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

编译器给出此警告的原因是因为将字段标记为易失性意味着“此字段将在多个线程上更新 - 不生成任何缓存该字段值的代码,并确保任何通过处理器缓存不一致性,该字段的读取或写入不会“及时向前和向后移动”。如果你没有详细了解volatile的含义以及它如何影响处理器缓存语义,那么你就不明白它是如何工作的,不应该使用volatile。无锁程序非常难以正确使用;确保你的程序是正确的,因为你明白它是如何工作的,而不是偶然的。)

现在假设你创建一个变量,它是一个volatile字段的别名,把裁判传给那个领域。在被调用方法内部,编译器没有任何理由知道引用需要具有易失性语义!编译器会高兴地为未能实现易失性字段规则的方法生成代码,但变量是易失性字段的。这可以彻底破坏你的无锁逻辑;假设总是一个易失性字段是总是用易失性语义访问。把它看作是挥发性的,而不是其他时候是没有意义的。你必须总是是一致的,否则你不能保证其他访问的一致性。

因此,当你这样做时编译器会发出警告,因为它可能会彻底搞乱你精心开发的无锁逻辑。

当然,Interlocked.Exchange 写的期望一个易变的领域,并做正确的事情。该警告因此具有误导性。我非常后悔,我们应该做的是实现一些机制,通过这种机制,像Interlocked.Exchange这样的方法的作者可以在方法中声明一个属性,说“这个方法需要一个ref来强制变量上的volatile语义,所以禁止警告”。也许在未来版本的编译器中,我们应该这样做。

+4

谢谢!最好的答案! – 2010-02-05 16:30:50

+0

从我所听到的Interlocked.Exchange也保证了内存屏障的创建。因此,如果您例如创建一个新对象,然后分配一些属性,然后将对象存储在另一个引用中而不使用Interlocked.Exchange,那么编译器可能会扰乱这些操作的顺序,从而导致访问第二个引用而不是线程 - 安全。那真的是吗?是否有意义使用Interlocked.Exchange是那种场景? – Mike 2010-12-14 14:17:50

+10

@Mike:当谈到低锁多线程情况下可能出现的情况时,我和下一个人一样无知。答案可能因处理器而异。你应该向专家提出你的问题,或者如果它对你感兴趣,请阅读这个主题。乔·达菲的书和他的博客是开始的好地方。我的规则:不要使用多线程。如果你必须使用不可变的数据结构。如果你不能,使用锁。只有当你*必须*具有无锁的可变数据时,才应该考虑低锁技术。 – 2010-12-14 15:05:14

9

要么你的同事错了,要么他知道C#语言规范没有的东西。

5.5 Atomicity of variable references

“读取和以下 数据类型的读写是原子:布尔,焦炭, 字节,为sbyte,短,USHORT,UINT,INT, 浮子,和引用类型”。

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

当然,您应该小心如何确定哪个线程应该获取新数据,从而最大限度地减少一次有多个线程执行此操作的风险。

+1

只要内存对齐正确, – zebrabox 2010-02-03 13:36:50

+2

@guffa:是的,我也读过。这留下了原来的问题“引用赋值是原子的,为什么Interlocked.Exchange(ref Object,Object)需要?”没有答案 – 2010-02-03 13:43:07

+0

@zebrabox:你是什么意思?当他们不是?你会怎么做? – 2010-02-03 13:44:13

6

Interlocked.Exchange< T >

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

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

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

2

Iterlocked.Exchange()不只是原子的,它也需要存储可见性照顾:

下同步功能使用适当的障碍,以保证内存排序:进入或离开关键部分

功能

信号同步对象的功能

等待功能

互锁功能

Synchronization and Multiprocessor Issues

这意味着,除了原子它可以确保:

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