在我的多线程ASMX Web服务中,我有一个我自己类型SystemData的类字段_AllData,它由少数组成 List<T>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

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

发出警告“对挥发性领域的引用不会被视为挥发性”,我该怎么看?

有帮助吗?

解决方案

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

参考分配是原子化的,所以为什么需要互锁(ref对象,对象)?

参考分配是原子。互锁。交换不仅仅是参考分配。它读取变量的当前值,将旧值藏起来,并将新值分配给变量,所有这些都作为原子操作。

我的同事说,在某些平台上,不能保证参考作业是原子。我的同事正确吗?

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

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

不必要。您的同事可能会出于不良理由给您很好的建议。也许还有其他原因为什么您应该使用interlocked.exchange。无锁的编程非常困难,当您偏离该领域专家所拥护的良好实践时,您就会摆脱杂草,并冒着最坏的种族条件的风险。我既不是该领域的专家,也不是您的代码专家,因此我无法以一种或另一种方式做出判断。

发出警告“对挥发性领域的引用不会被视为挥发性”,我该怎么看?

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

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

(我认为您已经理解了所有这些。如果您对挥发性含义及其如何影响处理器缓存语义没有详细的理解,那么您将不了解其工作原理,不应使用挥发性。很难正确正确;请确保您的程序正确,因为您了解它的工作原理,而不是偶然的。)

现在,假设您通过将REF传递到该字段来制作一个变量,这是挥发性字段的别名。在所谓的方法中,编译器没有理由知道参考需要具有挥发性语义!编译器将为未能实施挥发性字段的规则而愉快地生成代码,但是变量 一个动荡的场。这可能会完全破坏您的无锁逻辑;假设始终是挥发性领域是 总是 具有挥发性语义的访问。有时而不是其他时间将其视为挥发性是没有意义的。你必须 总是 保持一致,否则您无法保证其他访问的一致性。

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

当然,互锁。交换 写成期望一个动荡的领域并做正确的事。因此,警告是误导性的。我非常后悔。我们应该做的是实现某种机制,使互联网之类的方法的作者可以对该方法提出一个属性,说“该方法采用REF对变量进行挥发性语义,因此可以抑制警告”。也许在编译器的未来版本中,我们将这样做。

其他提示

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

5.5可变参考的原子性:

“以下数据类型的阅读和写作是原子:布尔,char,byte,sbyte,short,ushort,uint,int,int,float和参考类型。”

因此,您可以将其写入挥发性参考,而不会风险获得损坏的价值。

当然,您应该谨慎地确定哪个线程应获取新数据,以最大程度地降低一次多个线程的风险。

互锁.exchange <t>

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

它更改并返回原始价值,这是毫无用处的,因为您只想更改它,正如Guffa所说,它已经是原子。

除非被证明是您的应用程序中的瓶颈,否则您应该考虑删除锁,否则更容易理解并证明您的代码是正确的。

Iterlocked.Exchange() 不仅是原子质,还可以照顾记忆可见性:

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

输入或留下关键部分的功能

信号同步对象的函数

等待功能

互锁的功能

同步和多处理器问题

这意味着除原子性外,它还确保了:

  • 对于呼叫的线程:
    • 未完成指令(由编译器,运行时或硬件)进行重新排序。
  • 对于所有线程:
    • 在此说明之前,没有对记忆的读取,将看到该指令所做的更改。
    • 此说明之后的所有读取都将看到此说明所做的更改。
    • 在此指令更改达到主内存之后,将在此指令后进行记忆的所有写入(通过将此指令更改为主内存后,将其完成后,而不允许硬件在时间上齐平的硬件)。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top