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

在我的多线程 asmx Web 服务中,我有一个类 field _ allData,属于我自己的类型 SystemData,它由几个标记为 volatileList<T>Dictionary<T>组成。系统数据(_allData)每隔一段时间刷新一次,我通过创建另一个名为 newData的对象并用新数据填充它的数据结构来做到这一点。完成后,我就分配任务

private static volatile SystemData _allData


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

这应该可以工作,因为赋值是原子的,并且具有对旧数据的引用的线程继续使用它,而其他线程在赋值之后就具有新的系统数据。然而,我的同事说,我应该使用 InterLocked.Exchange,而不是使用 volatile关键字和简单的赋值,因为他说,在某些平台上,不能保证引用赋值是原子的。而且: 当我声明 the _allData字段为 volatile时,

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

produces warning "a reference to a volatile field will not be treated as volatile" What should I think about this?

34843 次浏览

要么是您的同事搞错了,要么是他知道一些 C # 语言规范所不知道的东西。

Atomicity of variable references:

”读写以下内容 数据类型是原子的: bool、 char、, byte, sbyte, short, ushort, uint, int, Float 和引用类型。”

因此,您可以写入易失性引用,而不会有得到损坏值的风险。

当然,对于如何决定哪个线程应该获取新数据,您应该非常小心,以尽量减少一次多个线程执行此操作的风险。

交换 < T >

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

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

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

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

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

Reference assignment is atomic. Interlocked.Exchange does not do only reference assignment. It does a read of the current value of a variable, stashes away the old value, and assigns the new value to the variable, all as an atomic operation.

我的同事说,在某些平台上,不能保证引用赋值是原子的。我的同事说得对吗?

No. Reference assignment is guaranteed to be atomic on all .NET platforms.

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

不一定。你的同事可能因为一些不好的原因给了你好的建议。也许还有其他原因,为什么你应该使用联锁。交换。无锁编程是非常困难的,一旦你偏离了这个领域的专家所支持的成熟实践,你就会陷入最糟糕的比赛环境。我既不是这个领域的专家,也不是你们代码的专家,所以我无法做出这样或那样的判断。

产生警告“一个易失性字段的引用将不被视为易失性”我应该如何看待这个问题?

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

编译器给出这个警告的原因是因为将一个字段标记为可变的意味着“这个字段将在多个线程上更新——不要生成任何缓存该字段值的代码,并确保对这个字段的任何读或写操作不会因为处理器缓存不一致而“向前和向后移动”

(我想你已经都明白了。如果您没有详细地了解 volic 的含义以及它如何影响处理器缓存语义,那么您就不了解它是如何工作的,因此不应该使用 volic。无锁程序很难正确运行; 请确保您的程序是正确的,因为您了解它是如何工作的,而不是偶然地正确。)

现在假设您通过向该字段传递一个 ref 来创建一个变量,该变量是可变字段的别名。在被调用的方法中,编译器没有任何理由知道引用需要具有易失性语义!编译器将愉快地为未能实现易失性字段规则的方法生成代码,但变量 是易失性字段。这可能会完全破坏您的无锁逻辑; 假设总是使用易失性语义访问易失性字段 一直都是。有时候把它看作易失性而不是其他时候是没有意义的; 您必须保证 一直都是的一致性,否则就不能保证其他访问的一致性。

因此,当您这样做时,编译器会发出警告,因为它可能会完全扰乱您精心开发的无锁逻辑。

当然,联锁。写入 Exchange以期望出现易失性字段并做正确的事情。因此,这一警告具有误导性。我对此非常遗憾; 我们本应该实现某种机制,使类似 Interlock 的方法的作者能够实现这种机制。Exchange 可以在该方法上放置一个属性,该属性表示“这个接受 ref 的方法对变量强制执行易失性语义,因此禁止发出警告”。也许在将来的编译器版本中我们会这样做。

Iterlocked.Exchange()不仅仅是原子的,它还关注内存可见性:

下列同步函数使用适当的屏障来确保内存顺序:

Functions that enter or leave critical sections

发出同步对象信号的函数

等待功能

联锁功能

同步和多处理器问题

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

  • 这个帖子的名字是:
    • 没有重新排序的指令(由编译器,运行时或硬件)。
  • 所有线索:
    • No reads from memory before this instruction will see changes to memory (by the thread that called this instruction) that happened after this instruction. This may sound obvious but cache lines may be flushed to main memory not in the order they were written to.
    • 此指令之后的所有读操作将看到此指令所做的更改以及此指令之前所做的所有更改(调用此指令的线程所做的更改)。
    • 在指令更改到达主存后,所有写入内存的操作都将发生(在指令更改完成时将指令更改刷新到主存,而不让硬件刷新自己的时间)。