c# - 易失性关键字使用与锁
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19382705/
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
c# - Volatile keyword usage vs lock
提问by Eric Ouellet
I've used volatile where I'm not sure it is necessary. I was pretty sure a lock would be overkill in my situation. Reading this thread (Eric Lippert comment) make me anxious on my usage of volatile: When should the volatile keyword be used in c# ?
我在不确定是否有必要的地方使用了 volatile。我很确定在我的情况下锁会过大。阅读此线程(Eric Lippert 评论)让我对 volatile 的使用感到焦虑:何时应在 c# 中使用 volatile 关键字?
I used volatile because my variable is use in a Multithreaded context where this variable could be accessed/modified concurrently, but where I can loose an addition without any hurts (see code).
我使用 volatile 是因为我的变量在多线程上下文中使用,在该上下文中可以同时访问/修改此变量,但是我可以在没有任何伤害的情况下松散添加(参见代码)。
I added "volatile" to make sure that there is no miss alignment occurring: reading only 32 bits of the variable and the other 32 bits on another fetch which can be broken in 2 by a write in the middle from another thread.
我添加了“易失性”以确保不会发生未命中对齐:仅读取变量的 32 位,而在另一个读取中读取其他 32 位,可以通过从另一个线程在中间写入而将其分解为 2。
Does my previous assumption (previous statement) can really happen of not ? If not, does "volatile" usage is still necessary (Option properties modifications could happen in any thread).
我之前的假设(之前的陈述)真的可以发生吗?如果不是,是否仍然需要使用“易失性”(选项属性修改可能发生在任何线程中)。
After reading the 2 first answers. I would like to insists on the fact that the way the code is written, it is not important if due to concurrency we miss an increment (want to increment from 2 threads but the result is only incremented by one due to concurrency) if at least the variable '_actualVersion' is incremented.
阅读第 2 个答案后。我想坚持这样一个事实,即代码的编写方式,如果由于并发我们错过了一个增量(想要从 2 个线程增加但结果由于并发而只增加一个)并不重要,如果至少变量 '_actualVersion' 递增。
As reference, this is the part of code where I'm using it. It is to report save action (write to disk) only while the application is idle.
作为参考,这是我使用它的代码部分。仅在应用程序空闲时报告保存操作(写入磁盘)。
public abstract class OptionsBase : NotifyPropertyChangedBase
{
private string _path;
volatile private int _savedVersion = 0;
volatile private int _actualVersion = 0;
// ******************************************************************
void OptionsBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
_actualVersion++;
Application.Current.Dispatcher.BeginInvoke(new Action(InternalSave), DispatcherPriority.ApplicationIdle);
}
// ******************************************************************
private void InternalSave()
{
if (_actualVersion != _savedVersion)
{
_savedVersion = _actualVersion;
Save();
}
}
// ******************************************************************
/// <summary>
/// Save Options
/// </summary>
private void Save()
{
using (XmlTextWriter writer = new XmlTextWriter(_path, null))
{
writer.Formatting = Formatting.Indented;
XmlSerializer x = new XmlSerializer(this.GetType());
x.Serialize(writer, this);
writer.Close();
}
}
采纳答案by Eric Lippert
I've used volatile where I'm not sure it is necessary.
我在不确定是否有必要的地方使用了 volatile。
Let me be very clear on this point:
在这一点上,让我非常清楚:
If you are not 100% clear on what volatile means in C# then do not use it.It is a sharp tool that is meant to be used by experts only. If you cannot describe what all the possible reorderings of memory accesses are allowed by a weak memory model architecture when two threads are reading and writing two different volatile fields then you do not know enough to use volatile safely and you will make mistakes, as you have done here, and write a program that is extremely brittle.
如果您不是 100% 清楚 volatile 在 C# 中的含义,请不要使用它。它是一种锋利的工具,仅供专家使用。如果您无法描述当两个线程正在读取和写入两个不同的 volatile 字段时弱内存模型架构所允许的所有可能的内存访问重新排序,那么您对安全使用 volatile 的了解还不够多,并且您会犯错误,因为您有到此完成,并编写一个极其脆弱的程序。
I was pretty sure a lock would be overkill in my situation
我很确定在我的情况下锁会过大
First off, the best solution is to simply not go there. If you don't write multithreaded code that tries to share memory then you don't have to worry about locking, which is hard to get correct.
首先,最好的解决办法就是不要去那里。如果您不编写尝试共享内存的多线程代码,那么您就不必担心锁定,这是很难得到正确的。
If you must write multithreaded code that shares memory, then the best practice is to alwaysuse locks. Locks are almost never overkill. The price of an uncontended lock is on the order of ten nanoseconds. Are you really telling me that ten extra nanoseconds will make a difference to your user? If so, then you have a very, very fast program and a user with unusually high standards.
如果您必须编写共享内存的多线程代码,那么最佳做法是始终使用锁。锁几乎从不矫枉过正。无竞争锁的价格约为 10 纳秒。您是真的告诉我,多出 10 纳秒会对您的用户产生影响吗?如果是这样,那么您有一个非常非常快的程序和一个具有异常高标准的用户。
The price of a contended lock is of course arbitrarily high if the code inside the lock is expensive. Do not do expensive work inside a lock, so that the probability of contention is low.
如果锁内的代码很昂贵,那么竞争锁的价格当然是任意高的。不要在锁内部做昂贵的工作,这样争用的概率就低。
Only when you have a demonstratedperformance problem with locks that cannot be solved by removing contention should you even begin to consider a low-lock solution.
只有当你有一个证明与不能消除竞争,你应该甚至开始考虑低锁的解决方案来解决锁性能问题。
I added "volatile" to make sure that there is no misalignment occurring: reading only 32 bits of the variable and the other 32 bits on another fetch which can be broken in two by a write in the middle from another thread.
我添加了“易失性”以确保不会发生错位:仅读取变量的 32 位,而在另一个读取中读取其他 32 位,可以通过从另一个线程中间写入将其分成两部分。
This sentence tells me that you need to stop writing multithreaded code right now. Multithreaded code, particularly low-lock code, is for experts only. You have to understand how the system actually works before you start writing multithreaded code again. Get a good book on the subject and study hard.
这句话告诉我,你现在需要停止编写多线程代码。多线程代码,尤其是低锁代码,仅供专家使用。在再次开始编写多线程代码之前,您必须了解系统的实际工作方式。找一本关于这个主题的好书并努力学习。
Your sentence is nonsensical because:
你的句子是荒谬的,因为:
First off, integers already are only 32 bits.
首先,整数已经只有 32 位。
Second, int accesses are guaranteed by the specification to be atomic! If you want atomicity, you've already got it.
其次,规范保证 int 访问是原子的!如果您想要原子性,那么您已经拥有了。
Third, yes, it is true that volatile accesses are always atomic, but that is not because C# makes all volatile accesses into atomic accesses! Rather, C# makes it illegal to put volatile on a field unless the field is alreadyatomic.
第三,是的,确实 volatile 访问始终是原子的,但这并不是因为 C# 将所有 volatile 访问都变成了原子访问!相反,C# 将 volatile 放在一个字段上是非法的,除非该字段已经是原子的。
Fourth, the purpose of volatile is to prevent the C# compiler, jitter and CPU from making certain optimizations that would change the meaning of your program in a weak memory model. Volatile in particular does not make ++ atomic. (I work for a company that makes static analyzers; I will use your code as a test case for our "incorrect non-atomic operation on volatile field" checker. It is very helpful to me to get real-world code that is full of realistic mistakes; we want to make sure that we are actually finding the bugs that people write, so thanks for posting this.)
第四,volatile 的目的是防止 C# 编译器、抖动和 CPU 进行某些优化,这些优化会在弱内存模型中改变程序的含义。尤其是挥发性不会使 ++ 原子化。(我在一家制作静态分析器的公司工作;我将使用您的代码作为我们“对 volatile 字段的不正确非原子操作”检查器的测试用例。获得充满真实世界的代码对我非常有帮助现实的错误;我们想确保我们确实找到了人们写的错误,所以感谢您发布此信息。)
Looking at your actual code: volatile is, as Hans pointed out, totally inadequate to make your code correct. The best thing to do is what I said before: do not allow these methods to be called on any thread other than the main thread. That the counter logic is wrong should be the least of your worries. What makes the serialization thread safe if code on another thread is modifying the fields of the object while it is being serialized?That is the problem you should be worried about first.
查看您的实际代码:正如 Hans 指出的那样, volatile 完全不足以使您的代码正确。最好的做法是我之前说过的:不允许在主线程以外的任何线程上调用这些方法。反逻辑是错误的应该是您最不担心的。如果另一个线程上的代码在对象被序列化时修改了对象的字段,那么什么使序列化线程安全?这是你首先应该担心的问题。
回答by Eric Ouellet
The comparison and assignment in your InternalSave() method would not be thread safe with or without the volatile keyword. If you would like to avoid using locks, you could use the CompareExchange() and Increment() methods in your code from the framework's Interlockedclass.
无论是否使用 volatile 关键字,InternalSave() 方法中的比较和赋值都不是线程安全的。如果您想避免使用锁,您可以在来自框架的Interlocked类的代码中使用 CompareExchange() 和 Increment() 方法。
回答by idipous
Regarding to your statement whether a variable could be split in two 32 bit fetches when using volatile, this could be a possibility if you were using something bigger than Int32.
关于您在使用 volatile 时是否可以将变量拆分为两个 32 位提取的声明,如果您使用的是大于 Int32 的内容,这可能是可能的。
So as long as you are using Int32 you do not have an issue with what you are stating.
因此,只要您使用的是 Int32,您就不会遇到您所陈述的问题。
However, as you have read in the suggested link volatile gives you only weak guarantees and I would prefer locks so as to be on the safe side as today's machines have more than one CPU and volatile does not guarantee that another CPU won't sweep in and do something unexpected.
但是,正如您在建议的链接中阅读的那样, volatile 仅提供了较弱的保证,我更喜欢锁定以确保安全,因为今天的机器有多个 CPU,而 volatile 并不能保证另一个 CPU 不会进入并做一些意想不到的事情。
EDIT
编辑
Have you considered using Interlocked.Increment?
您是否考虑过使用 Interlocked.Increment?
回答by Hans Passant
Volatileis woefully inadequate to make this code safe. You can use low-level locking with Interlocked.Increment() and Interlocked.CompareExchange() but there's very little reason to assume that Save() is thread-safe. It sure looks like it tries to save an object that's being modified by a worker thread.
可悲的是,Volatile不足以使此代码安全。您可以将低级锁定与 Interlocked.Increment() 和 Interlocked.CompareExchange() 一起使用,但几乎没有理由假设 Save() 是线程安全的。看起来它确实试图保存一个正在被工作线程修改的对象。
Using lockis very strongly indicated here, not just to protect the version numbers but also to prevent the object from changing while it is being serialized. The corrupted saves you'll get from not doing this are entirely too infrequent to ever have a shot a debugging the problem.
在这里非常强烈地表明使用锁,不仅是为了保护版本号,而且是为了防止对象在序列化时发生变化。如果不这样做,您将获得的损坏的保存完全太罕见,以至于无法调试问题。
回答by Bob Bryan
According to Joe Albahari's excellent post on threads and lockswhich is from his equally excellent book C# 5.0 In A Nutshell, he says herethat even when the volatile keyword is used, that a write statement followed by a read statement can be reordered.
根据 Joe Albahari关于线程和锁的出色帖子,该帖子来自他同样出色的书 C# 5.0 In A Nutshell,他在这里说,即使使用 volatile 关键字,也可以重新排序后跟读语句的写语句。
Further down, he says that MSDN documentation on this topic is incorrect and suggests that there is a strong case to be made for avoiding the volatile keyword altogether. He points out that even if you happen to understand the subtleties involved, will other developers down the line also understand it?
再往下,他说关于这个主题的 MSDN 文档是不正确的,并建议有充分的理由完全避免 volatile 关键字。他指出,即使你碰巧明白其中的微妙之处,其他开发人员也会明白吗?
So, using a lock is not only more "correct", it is also more understandable, offers ease of adding a new feature that adds multiple update statements to the code in an atomic fashion - which neither volatile nor a fence class like MemoryBarriercan do, is very fast, and is much easier to maintain since there is much less chance of introducing a subtle bug by less experienced developers down the line.
因此,使用锁不仅更“正确”,而且更易于理解,可以轻松添加新功能,以原子方式向代码添加多个更新语句 - 这既不是 volatile 也不是像MemoryBarrier这样的栅栏类, 非常快,并且更容易维护,因为经验不足的开发人员引入细微错误的可能性要小得多。