cha*_*r m 104 c# multithreading atomicity volatility
在我的多线程ASMX web服务,我有我自己的类型SystemData的类别字段_allData它由数List<T>和Dictionary<T>标记为volatile.系统data(_allData)会偶尔刷新一次,我会通过创建另一个被调用的对象newData并用新数据填充它的数据结构来实现.当它完成后我就分配了
private static volatile SystemData _allData
public static bool LoadAllSystemData()
{
SystemData newData = new SystemData();
/* fill newData with up-to-date data*/
...
_allData = newData.
}
Run Code Online (Sandbox Code Playgroud)
这应该工作,因为赋值是原子的,并且具有对旧数据的引用的线程继续使用它,而其余的在分配之后具有新的系统数据.然而,我的同事说,volatile我应该使用,而不是使用关键字和简单的InterLocked.Exchange分配,因为他说在某些平台上,不能保证引用赋值是原子的.另外:当我宣布the _allData字段volatile的
Interlocked.Exchange<SystemData>(ref _allData, newData);
Run Code Online (Sandbox Code Playgroud)
产生警告"对易变场的引用不会被视为不稳定的"我应该怎么看待这个?
Eri*_*ert 173
这里有很多问题.一次考虑一个:
引用赋值是原子的,为什么需要Interlocked.Exchange(ref Object,Object)?
引用赋值是原子的.Interlocked.Exchange不仅仅执行引用赋值.它读取变量的当前值,隐藏旧值,并将新值分配给变量,所有这些都作为原子操作.
我的同事说,在某些平台上,不能保证引用赋值是原子的.我的同事是对的吗?
在所有.NET平台上,参考分配都保证是原子的.
我的同事正在虚假处理.这是否意味着他们的结论不正确?
不必要.你的同事可能会因为不好的理由给你很好的建议.也许还有一些其他原因你应该使用Interlocked.Exchange.无锁编程非常困难,当您离开该领域专家所支持的成熟实践的那一刻,您就会处于杂草之中并冒着最恶劣的竞争条件.我不是这个领域的专家,也不是你的代码专家,所以我不能以某种方式作出判断.
产生警告"对易变场的引用不会被视为不稳定的"我应该怎么看待这个?
你应该理解为什么这是一般的问题.这将导致理解为什么警告在这种特殊情况下不重要.
编译器发出此警告的原因是因为将字段标记为volatile意味着"此字段将在多个线程上更新 - 不生成任何缓存此字段值的代码,并确保任何读取或写入这个字段不是通过处理器缓存不一致"及时向前和向后移动"."
(我假设你已经理解了所有这些.如果你没有详细了解volatile的含义以及它如何影响处理器缓存语义,那么你就不明白它是如何工作的,不应该使用volatile.无锁程序很难做到正确;确保你的程序是正确的,因为你了解它是如何工作的,而不是偶然的.)
现在假设您通过将ref传递给该字段来创建一个变量,该变量是volatile字段的别名.在被调用的方法中,编译器没有任何理由知道引用需要具有volatile语义!编译器将为无法实现volatile字段规则的方法快速生成代码,但变量是 volatile字段.这完全破坏了你的无锁逻辑; 假设始终是挥发性场总挥发性语义访问.有时将其视为易变性是没有意义的,而不是其他时间; 你必须始终保持一致,否则你无法保证其他访问的一致性.
因此,编译器会在您执行此操作时发出警告,因为它可能会完全破坏您精心开发的无锁逻辑.
当然,Interlocked.Exchange 被编写为期望一个易变的字段并做正确的事情.因此该警告具有误导性.我非常后悔; 我们应该做的是实现一些机制,其中像Interlocked.Exchange这样的方法的作者可以在方法上放置一个属性,说"这个方法采用ref对变量强制执行volatile语义,因此禁止警告".也许在未来版本的编译器中我们将这样做.
你的同事是错的,或者他知道C#语言规范没有的东西.
"以下数据类型的读写是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference types."
因此,您可以写入易失性参考,而不会有获得损坏值的风险.
您当然应该小心决定哪个线程应该获取新数据,以最大限度地降低一次多个线程执行此操作的风险.
Interlocked.Exchange()不仅仅是原子性的,它还负责内存可见性:
以下同步函数使用适当的屏障来确保内存排序:
进入或离开临界区的函数
向同步对象发出信号的函数
等待功能
联锁功能
这意味着除了原子性之外,它还确保:
将指定类型T的变量设置为指定值,并将原始值作为原子操作返回.
它改变并返回原始值,它没用,因为你只想改变它,正如Guffa所说,它已经是原子的.
除非分析器被证明是您应用程序中的瓶颈,否则您应该考虑使用unsing锁,更容易理解并证明您的代码是正确的.