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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2192124/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 00:16:02  来源:igfitidea点击:

reference assignment is atomic so why is Interlocked.Exchange(ref Object, Object) needed?

c#multithreadingatomicityvolatility

提问by char m

In my multithreaded asmx web service I had a class field _allData of my own type SystemData which consists of few List<T>and Dictionary<T>marked as volatile. The system data (_allData) is refreshed once in a while and I do it by creating another object called newDataand fill it's data structures with new data. When it's done I just assign

在我的多线程 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.
} 

This should work since the assignment is atomic and the threads that have the reference to old data keep using it and the rest have the new system data just after assignment. However my collegue said that instead of using volatilekeyword and simple assigment I should use InterLocked.Exchangebecause he said that on some platforms it's not guaranteed that reference assignment is atomic. Moreover: when I declare the _allDatafield as volatilethe

这应该可以工作,因为分配是原子的,并且引用旧数据的线程继续使用它,其余的线程在分配后立即拥有新的系统数据。但是我的同事说volatile我应该使用关键字和简单的InterLocked.Exchange赋值而不是使用关键字和简单的赋值,因为他说在某些平台上不能保证引用赋值是原子的。另外:当我宣布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?

产生警告“对易失性字段的引用不会被视为易失性”我应该怎么想?

采纳答案by Eric Lippert

There are numerous questions here. Considering them one at a time:

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

reference assignment is atomic so why is Interlocked.Exchange(ref Object, Object) needed?

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

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.

引用分配是原子的。Interlocked.Exchange 不只做引用分配。它读取变量的当前值,隐藏旧值,并将新值分配给变量,所有这些都是原子操作。

my colleague said that on some platforms it's not guaranteed that reference assignment is atomic. Was my colleague correct?

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

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

否。引用分配保证在所有 .NET 平台上都是原子的。

My colleague is reasoning from false premises. Does that mean that their conclusions are incorrect?

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

Not necessarily. Your colleague could be giving you good advice for bad reasons. Perhaps there is some other reason why you ought to be using Interlocked.Exchange. Lock-free programming is insanely difficult and the moment you depart from well-established practices espoused by experts in the field, you are off in the weeds and risking the worst kind of race conditions. I am neither an expert in this field nor an expert on your code, so I cannot make a judgement one way or the other.

不必要。你的同事可能会出于不好的原因给你很好的建议。也许您应该使用 Interlocked.Exchange 的原因还有其他一些。无锁编程是非常困难的,一旦你偏离了该领域专家所支持的成熟实践,你就会陷入困境并冒着最糟糕的竞争条件的风险。我既不是该领域的专家,也不是您的代码方面的专家,因此我无法以任何方式做出判断。

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

产生警告“对易失性字段的引用不会被视为易失性”我应该怎么想?

You should understand why this is a problem in general. That will lead to an understanding of why the warning is unimportant in this particular case.

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

The reason that the compiler gives this warning is because marking a field as volatile means "this field is going to be updated on multiple threads -- do not generate any code that caches values of this field, and make sure that any reads or writes of this field are not "moved forwards and backwards in time" via processor cache inconsistencies."

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

(I assume that you understand all that already. If you do not have a detailed understanding of the meaning of volatile and how it impacts processor cache semantics then you don't understand how it works and should not be using volatile. Lock-free programs are very difficult to get right; make sure that your program is right because you understand how it works, not right by accident.)

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

Now suppose you make a variable which is an alias of a volatile field by passing a ref to that field. Inside the called method, the compiler has no reason whatsoever to know that the reference needs to have volatile semantics! The compiler will cheerfully generate code for the method that fails to implement the rules for volatile fields, but the variable isa volatile field. That can completely wreck your lock-free logic; the assumption is always that a volatile field is alwaysaccessed with volatile semantics. It makes no sense to treat it as volatile sometimes and not other times; you have to alwaysbe consistent otherwise you cannot guarantee consistency on other accesses.

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

Therefore, the compiler warns when you do this, because it is probably going to completely mess up your carefully developed lock-free logic.

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

Of course, Interlocked.Exchange iswritten to expect a volatile field and do the right thing. The warning is therefore misleading. I regret this very much; what we should have done is implement some mechanism whereby an author of a method like Interlocked.Exchange could put an attribute on the method saying "this method which takes a ref enforces volatile semantics on the variable, so suppress the warning". Perhaps in a future version of the compiler we shall do so.

当然,Interlocked.Exchange编写为期望一个易变的字段并做正确的事情。因此,该警告具有误导性。我非常后悔;我们应该做的是实现某种机制,通过这种机制,像 Interlocked.Exchange 这样的方法的作者可以在方法上放置一个属性,说“这个采用 ref 的方法对变量强制执行易失性语义,因此禁止警告”。也许在编译器的未来版本中我们会这样做。

回答by Guffa

Either your collegue is mistaken, or he knows something that the C# language specification doesn't.

要么你的同事弄错了,要么他知道 C# 语言规范不知道的东西。

5.5 Atomicity of variable references:

5.5 变量引用的原子性

"Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types."

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

So, you can write to the volatile reference without risk of getting a corrupted value.

因此,您可以写入 volatile 引用而不会冒损坏值的风险。

You should of course be careful with how you decide which thread should fetch the new data, to minimise the risk that more than one thread at a time does that.

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

回答by Guillaume

Interlocked.Exchange< T >

Interlocked.Exchange<T>

Sets a variable of the specified type T to a specified value and returns the original value, as an atomic operation.

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

It changes and returns the original value, it's useless because you only want to change it and, as Guffa said, it's already atomic.

它改变并返回原始值,它没有用,因为你只想改变它,正如 Guffa 所说,它已经是原子的。

Unless a profiler as proven it to be a bottleneck in your application, you should consider unsing locks, it's easier to understand and prove that your code is right.

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

回答by selalerer

Iterlocked.Exchange()is not just atomic, it also takes care of memory visibility:

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

The following synchronization functions use the appropriate barriers to ensure memory ordering:

Functions that enter or leave critical sections

Functions that signal synchronization objects

Wait functions

Interlocked functions

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

进入或离开临界区的函数

通知同步对象的函数

等待功能

联锁功能

Synchronization and Multiprocessor Issues

同步和多处理器问题

This means that in addition to atomicity it ensures that:

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

  • For the thread calling it:
    • No reordering of the instructions is done (by the compiler, the run-time or the hardware).
  • For all threads:
    • No reads to memory that happen before this instruction will see the change this instruction made.
    • All reads after this instruction will see the change made by this instruction.
    • All writes to memory after this instruction will happen after this instruction change has reached the main memory (by flushing this instruction change to main memory when its done and not let the hardware flush it own its on timing).
  • 对于调用它的线程:
    • 没有重新排序指令(由编译器、运行时或硬件)。
  • 对于所有线程:
    • 在此指令之前发生的任何对内存的读取都不会看到此指令所做的更改。
    • 此指令之后的所有读取都将看到此指令所做的更改。
    • 在此指令更改到达主内存后,将在此指令之后写入内存(通过在完成时将此指令更改刷新到主内存,而不是让硬件刷新它自己的定时)。