何时在C#中使用volatile关键字?

本文翻译自:When should the volatile keyword be used in C#?

Can anyone provide a good explanation of the volatile keyword in C#? 谁能对C#中的volatile关键字提供很好的解释? Which problems does it solve and which it doesn't? 它可以解决哪些问题,哪些不能解决? In which cases will it save me the use of locking? 在什么情况下可以节省锁定的使用时间?


#1楼

参考:https://stackoom.com/question/iNJ/何时在C-中使用volatile关键字


#2楼

I don't think there's a better person to answer this than Eric Lippert (emphasis in the original): 我认为没有比埃里克·利珀特Eric Lippert)更好的人了(原文强调):

In C#, "volatile" means not only "make sure that the compiler and the jitter do not perform any code reordering or register caching optimizations on this variable". 在C#中,“ volatile”不仅意味着“确保编译器和抖动不会对此变量执行任何代码重新排序或寄存器缓存优化”。 It also means "tell the processors to do whatever it is they need to do to ensure that I am reading the latest value, even if that means halting other processors and making them synchronize main memory with their caches". 这也意味着“告诉处理器去做我需要做的所有事情,以确保我正在读取最新的值,即使这意味着停止其他处理器并使它们将主内存与其缓存同步”。

Actually, that last bit is a lie. 实际上,最后一点是谎言。 The true semantics of volatile reads and writes are considerably more complex than I've outlined here; 易失性读写的真正语义比我在这里概述的要复杂得多。 in fact they do not actually guarantee that every processor stops what it is doing and updates caches to/from main memory. 实际上, 它们实际上并不能保证每个处理器都会停止正在执行的操作并向/从主内存更新缓存。 Rather, they provide weaker guarantees about how memory accesses before and after reads and writes may be observed to be ordered with respect to each other . 相反, 它们提供了关于读写前后如何观察到的内存访问相对顺序的较弱保证 Certain operations such as creating a new thread, entering a lock, or using one of the Interlocked family of methods introduce stronger guarantees about observation of ordering. 诸如创建新线程,输入锁或使用互锁方法系列之一之类的某些操作为观察顺序提供了更有力的保证。 If you want more details, read sections 3.10 and 10.5.3 of the C# 4.0 specification. 如果需要更多详细信息,请阅读C#4.0规范的3.10和10.5.3节。

Frankly, I discourage you from ever making a volatile field . 坦白说, 我不鼓励您从事不稳定的领域 Volatile fields are a sign that you are doing something downright crazy: you're attempting to read and write the same value on two different threads without putting a lock in place. 易失性字段表明您正在疯狂地做某事:您试图在两个不同的线程上读取和写入相同的值,而没有进行锁定。 Locks guarantee that memory read or modified inside the lock is observed to be consistent, locks guarantee that only one thread accesses a given chunk of memory at a time, and so on. 锁可确保观察到在锁内读取或修改的内存是一致的,锁可确保一次只有一个线程访问给定的内存块,依此类推。 The number of situations in which a lock is too slow is very small, and the probability that you are going to get the code wrong because you don't understand the exact memory model is very large. 锁太慢的情况的数量非常少,并且由于您不了解确切的内存模型而导致代码出错的可能性非常大。 I don't attempt to write any low-lock code except for the most trivial usages of Interlocked operations. 除了互锁操作的最简单用法之外,我不会尝试编写任何低锁代码。 I leave the usage of "volatile" to real experts. 我将“易失性”的用法留给真正的专家使用。

For further reading see: 有关更多阅读,请参阅:


#3楼

multiple threads can access a variable. 多个线程可以访问一个变量。 The latest update will be on the variable 最新更新将在变量上


#4楼

If you are using .NET 1.1, the volatile keyword is needed when doing double checked locking. 如果使用的是.NET 1.1,则在进行双重检查锁定时需要volatile关键字。 Why? 为什么? Because prior to .NET 2.0, the following scenario could cause a second thread to access an non-null, yet not fully constructed object: 因为在.NET 2.0之前,以下情况可能导致第二个线程访问非空但尚未完全构造的对象:

  1. Thread 1 asks if a variable is null. 线程1询问变量是否为空。 //if(this.foo == null) //if(this.foo == null)
  2. Thread 1 determines the variable is null, so enters a lock. 线程1确定变量为null,因此输入一个锁。 //lock(this.bar) //锁定(this.bar)
  3. Thread 1 asks AGAIN if the variable is null. 线程1再次询问变量是否为null。 //if(this.foo == null) //if(this.foo == null)
  4. Thread 1 still determines the variable is null, so it calls a constructor and assigns the value to the variable. 线程1仍确定该变量为null,因此它调用构造函数并将该值分配给该变量。 //this.foo = new Foo(); //this.foo = new Foo();

Prior to .NET 2.0, this.foo could be assigned the new instance of Foo, before the constructor was finished running. 在.NET 2.0之前,可以在构造函数完成运行之前为this.foo分配新的Foo实例。 In this case, a second thread could come in (during thread 1's call to Foo's constructor) and experience the following: 在这种情况下,第二个线程可能会进入(在线程1对Foo的构造函数的调用期间),并且会遇到以下情况:

  1. Thread 2 asks if variable is null. 线程2询问变量是否为null。 //if(this.foo == null) //if(this.foo == null)
  2. Thread 2 determines the variable is NOT null, so tries to use it. 线程2确定该变量不为null,因此尝试使用它。 //this.foo.MakeFoo() //this.foo.MakeFoo()

Prior to .NET 2.0, you could declare this.foo as being volatile to get around this problem. 在.NET 2.0之前,您可以将this.foo声明为volatile,以解决此问题。 Since .NET 2.0, you no longer need to use the volatile keyword to accomplish double checked locking. 从.NET 2.0开始,您不再需要使用volatile关键字来完成双重检查锁定。

Wikipedia actually has a good article on Double Checked Locking, and briefly touches on this topic: http://en.wikipedia.org/wiki/Double-checked_locking Wikipedia实际上有一篇关于Double Checked Locking的好文章,并简要介绍了该主题: http : //en.wikipedia.org/wiki/Double-checked_locking


#5楼

The compiler sometimes changes the order of statements in code to optimize it. 编译器有时会更改代码中语句的顺序以对其进行优化。 Normally this is not a problem in single-threaded environment, but it might be an issue in multi-threaded environment. 通常,在单线程环境中这不是问题,但在多线程环境中则可能是问题。 See following example: 请参见以下示例:

 private static int _flag = 0;
 private static int _value = 0;

 var t1 = Task.Run(() =>
 {
     _value = 10; /* compiler could switch these lines */
     _flag = 5;
 });

 var t2 = Task.Run(() =>
 {
     if (_flag == 5)
     {
         Console.WriteLine("Value: {0}", _value);
     }
 });

If you run t1 and t2, you would expect no output or "Value: 10" as the result. 如果运行t1和t2,则不会输出任何结果或出现“ Value:10”。 It could be that the compiler switches line inside t1 function. 可能是编译器在t1函数内部切换了行。 If t2 then executes, it could be that _flag has value of 5, but _value has 0. So expected logic could be broken. 如果然后执行t2,则可能是_flag的值为5,但_value的值为0。因此可能会破坏预期的逻辑。

To fix this you can use volatile keyword that you can apply to the field. 要解决此问题,您可以使用可应用于字段的volatile关键字。 This statement disables the compiler optimizations so you can force the correct order in you code. 该语句禁用编译器优化,因此您可以在代码中强制使用正确的顺序。

private static volatile int _flag = 0;

You should use volatile only if you really need it, because it disables certain compiler optimizations, it will hurt performance. 仅在真正需要时才应使用volatile ,因为它会禁用某些编译器优化,这会损害性能。 It's also not supported by all .NET languages (Visual Basic doesn't support it), so it hinders language interoperability. 并非所有.NET语言都支持它(Visual Basic不支持它),因此它阻碍了语言的互操作性。


#6楼

So to sum up all this, the correct answer to the question is: If your code is running in the 2.0 runtime or later, the volatile keyword is almost never needed and does more harm than good if used unnecessarily. 因此,总而言之,对该问题的正确答案是:如果您的代码在2.0运行时或更高版本中运行,则几乎不需要volatile关键字,如果不必要地使用volatile关键字,则弊大于利。 IE Don't ever use it. IE永远不要使用它。 BUT in earlier versions of the runtime, it IS needed for proper double check locking on static fields. 但是在运行时的早期版本中,需要对静态字段进行适当的双重检查锁定。 Specifically static fields whose class has static class initialization code. 特别是其类具有静态类初始化代码的静态字段。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值