在我的多线程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”我应该怎么看待这个?
答案 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)
将指定类型T的变量设置为指定值,并返回原始值作为原子操作。
它改变并返回原始值,它没用,因为你只想改变它,正如Guffa所说,它已经是原子的。
除非将探查器证明是您应用程序的瓶颈,否则您应该考虑使用unsing锁,更容易理解并证明您的代码是正确的。
答案 3 :(得分:2)
Iterlocked.Exchange()
不仅仅是原子的,它还负责内存可见性:
以下同步函数使用适当的障碍来确保内存排序:
进入或离开关键部分的功能
发出同步对象信号的函数
等待功能
互锁功能
Synchronization and Multiprocessor Issues
这意味着除了原子性之外,还可以确保: