引用分配是原子的,那么为什么需要 Interlocked.Exchange(ref Object, Object) ?
在我的多线程 asmx Web 服务中,我有一个我自己的 SystemData 类型的类字段 _allData,它由一些标记为 volatile 的 List
和 Dictionary
组成。系统数据 (_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 关键字和简单的赋值,因为他说在某些平台上不能保证引用赋值是原子的。此外:当我将_allData
字段声明为易失性
时,
Interlocked.Exchange<SystemData>(ref _allData, newData);
会产生警告“对易失性字段的引用不会被视为易失性”我应该对此有何看法?
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 newData
and fill it's data structures with new data. When it's done I just assign
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 volatile
keyword and simple assigment I should use InterLocked.Exchange
because he said that on some platforms it's not guaranteed that reference assignment is atomic. Moreover: when I declare the _allData
field as volatile
the
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这里有很多问题。一次考虑一个:
引用分配是原子的。 Interlocked.Exchange 不仅仅进行引用分配。它读取变量的当前值,隐藏旧值,并将新值分配给变量,所有这些都是原子操作。
不会。在所有 .NET 平台上,引用分配保证是原子的。
未必。你的同事可能会出于不好的原因给你提供好的建议。也许还有其他一些原因说明您应该使用 Interlocked.Exchange。无锁编程非常困难,一旦你偏离了该领域专家所拥护的既定实践,你就陷入了困境,并面临着最糟糕的竞争条件的风险。我既不是这个领域的专家,也不是你的代码的专家,所以我无法以某种方式做出判断。
您应该了解为什么这是一个普遍问题。这将有助于理解为什么警告在这种特殊情况下并不重要。
编译器给出此警告的原因是因为将字段标记为 易失性意味着“该字段将在多个线程上更新 - 不要生成任何缓存该字段值的代码,并确保任何读取或写入该字段不会因处理器缓存不一致而“在时间上前后移动”。
(我假设您已经了解了所有这些。如果您没有详细了解 易失性 的含义以及它如何影响处理器缓存语义,那么您就不了解它是如何工作的,并且不应该使用易失性。无锁程序很难做到正确;确保您的程序是正确的,因为您了解它是如何工作的,而不是偶然正确的。)
现在假设您通过将引用传递给该字段来创建一个变量,该变量是该字段的别名。在被调用的方法内部,编译器没有任何理由知道引用需要具有易失性语义!编译器会很高兴地为无法实现易失性字段规则的方法生成代码,但变量是一个易失性字段。这会完全破坏你的无锁逻辑;假设始终是易失性字段总是使用易失性语义进行访问。有时将其视为不稳定的,而有时将其视为不稳定的,这是没有意义的;您必须始终保持一致,否则您无法保证其他访问的一致性。
因此,当你这样做时,编译器会发出警告,因为它可能会完全搞乱你精心开发的无锁逻辑。
当然,Interlocked.Exchange 的编写目的是期望一个易失性字段并执行正确的操作。因此,该警告具有误导性。对此我感到非常遗憾;我们应该做的是实现某种机制,通过这种机制,像 Interlocked.Exchange 这样的方法的作者可以在该方法上放置一个属性,说明“此采用引用的方法对变量强制执行易失性语义,因此抑制警告”。也许在编译器的未来版本中我们会这样做。
There are numerous questions here. Considering them one at a time:
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.
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.
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."
(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.)
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 is a volatile field. That can completely wreck your lock-free logic; the assumption is always that a volatile field is always accessed with volatile semantics. It makes no sense to treat it as volatile sometimes and not other times; you have to always be consistent otherwise you cannot guarantee consistency on other accesses.
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 is written 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.
要么你的同事错了,要么他知道 C# 语言规范所不知道的东西。
变量引用的原子性:
因此,您可以写入易失性引用,而不会有获得损坏值的风险。
当然,您应该小心如何决定哪个线程应该获取新数据,以最大程度地减少多个线程获取新数据的风险一次线程就可以做到这一点。
Either your collegue is mistaken, or he knows something that the C# language specification doesn't.
Atomicity of variable references:
So, you can write to the volatile reference without risk of getting a corrupted value.
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.
Interlocked.Exchange< T>
它改变并返回原始值,它没有用,因为你只想改变它,正如 Guffa 所说,它已经是原子的了。
除非探查器证明它是应用程序中的瓶颈,否则您应该考虑使用锁,因为它更容易理解并证明您的代码是正确的。
Interlocked.Exchange< 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.
Unless a profiler as proven it to be a bottleneck in your application, you should consider using locks, as it's easier to understand and to prove that your code is right.
Interlocked.Exchange() 不仅仅是原子性的,它还负责内存可见性:
同步和多处理器问题
这意味着除了原子性之外,它还确保:
Interlocked.Exchange()
is not just atomic, it also takes care of memory visibility:Synchronization and Multiprocessor Issues
This means that in addition to atomicity it ensures that: