一尘不染

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

c#

在我的多线程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);

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


阅读 491

收藏
2020-05-19

共1个答案

一尘不染

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

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

参考分配是原子的。Interlocked.Exchange不只引用分配。它会读取变量的当前值,将旧值存储起来,并将新值分配给变量,所有这些操作都是原子操作。

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

不能。在所有.NET平台上,引用分配都是原子的。

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

不必要。您的同事出于不好的原因可能会给您很好的建议。也许还有其他原因使您应该使用Interlocked.Exchange。无锁编程非常困难,一旦您偏离了该领域专家公认的成熟实践,您就会陷入困境,并冒着最恶劣的比赛条件的危险。我既不是该领域的专家,也不是您的代码的专家,所以我无法做出一种判断。

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

您应该了解为什么这通常是一个问题。这将导致理解为什么在此特定情况下警告不重要。

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

(我假设您已经了解了所有内容。如果您对volatile的含义及其对处理器缓存语义的影响没有详细的了解,那么您将不了解它的工作原理,并且不应该使用volatile。无锁程序很难做到正确;请确保您的程序是正确的,因为您了解程序的工作原理,而不是偶然的结果。)

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

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

当然,Interlocked.Exchange 编写
为了期待一个易变的字段并做正确的事。因此,该警告具有误导性。我对此感到非常遗憾。我们应该做的是实现一种机制,通过这种机制,像Interlocked.Exchange这样的方法的作者可以在该方法上添加一个属性,说“该方法使用ref会对变量施加易变义的语义,从而消除警告”。也许在将来的编译器版本中,我们会这样做。

2020-05-19