有人能很好地解释一下c#中的volatile关键字吗?它能解决哪些问题,不能解决哪些问题?在哪些情况下,它将节省我使用锁定?


当前回答

从MSDN: volatile修饰符通常用于由多个线程访问而不使用lock语句序列化访问的字段。使用volatile修饰符可确保一个线程检索到另一个线程写入的最新值。

其他回答

编译器有时会改变代码中语句的顺序来优化它。通常这在单线程环境中不是问题,但在多线程环境中可能是问题。请看下面的例子:

 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);
     }
 });

如果运行t1和t2,您将期望没有输出或结果为“Value: 10”。可能是编译器在t1函数内部切换了行。如果t2执行,可能是_flag值为5,而_value值为0。因此,预期的逻辑可能会被打破。

为了解决这个问题,你可以使用volatile关键字,你可以应用到字段。此语句禁用编译器优化,以便您可以强制代码中的正确顺序。

private static volatile int _flag = 0;

只有在真正需要时才应该使用volatile,因为它会禁用某些编译器优化,这会影响性能。它也不是所有。net语言都支持(Visual Basic不支持),因此它阻碍了语言互操作性。

只需查看volatile关键字的官方页面,您就可以看到典型用法的示例。

public class Worker
{
    public void DoWork()
    {
        bool work = false;
        while (!_shouldStop)
        {
            work = !work; // simulate some work
        }
        Console.WriteLine("Worker thread: terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    
    private volatile bool _shouldStop;
}

将volatile修饰符添加到_shouldStop的声明中,您将总是得到相同的结果。但是,如果_shouldStop成员上没有这个修饰符,行为是不可预测的。

所以这绝对不是完全疯狂的事情。

存在缓存一致性,负责CPU缓存的一致性。

如果CPU采用强内存模型(如x86)

因此,volatile字段的读写在x86上不需要特殊的指令:普通的读写(例如,使用MOV指令)就足够了。

示例来自c# 5.0规范(第10.5.3章)

using System;
using System.Threading;
class Test
{
    public static int result;   
    public static volatile bool finished;
    static void Thread2() {
        result = 143;    
        finished = true; 
    }
    static void Main() {

        finished = false;
        new Thread(new ThreadStart(Thread2)).Start();

        for (;;) {
            if (finished) {
                Console.WriteLine("result = {0}", result);
                return;
            }
        }
    }
}

产生输出:result = 143

如果finished字段没有被声明为volatile,那么在store to finished之后,允许主线程可以看到store to result,因此主线程可以从字段结果中读取值0。

Volatile行为依赖于平台,所以你应该在需要的时候考虑使用Volatile,以确保它能满足你的需求。

即使是volatile也不能阻止(所有类型的)重排序(c# - c#内存模型的理论与实践,第2部分)

尽管对A的写操作是不稳定的,从A_Won的读操作也是不稳定的,但栅栏都是单向的,实际上允许这种重新排序。

所以我相信,如果你想知道什么时候使用volatile (vs lock vs Interlocked),你应该熟悉内存围栏(full, half)和同步的需求。那为了你自己,你自己去找答案吧。

有时候,编译器会优化一个字段并使用寄存器来存储它。如果线程1写了字段,而另一个线程访问了它,因为更新存储在寄存器(而不是内存)中,第二个线程将得到陈旧的数据。

你可以把volatile关键字看作是对编译器说“我想让你把这个值存储在内存中”。这保证了第二个线程检索到最新的值。

多个线程可以访问一个变量。 最新的更新将在变量上

我发现Joydip Kanjilal的这篇文章非常有用!

当您将一个对象或变量标记为volatile时,它将成为volatile读写的候选对象。需要注意的是,在c#中,所有的内存写操作都是volatile的,不管你写的是volatile对象还是非volatile对象。但是,当读取数据时,就会出现这种不确定性。当读取非易失性数据时,执行线程可能总是获得最新的值,也可能不总是。如果对象是volatile,线程总是获得最新的值

我就把它放在这里,供大家参考