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


当前回答

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

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

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

其他回答

如果你使用的是。net 1.1,在进行双重检查锁定时需要volatile关键字。为什么?因为在。net 2.0之前,下面的场景可能会导致第二个线程访问一个非空的,但还没有完全构造的对象:

线程1询问变量是否为空。 / /如果(这一点。Foo == null) 线程1确定变量为空,因此进入一个锁。 / /锁(this.bar) 线程1再次询问变量是否为空。 / /如果(这一点。Foo == null) 线程1仍然确定变量为空,因此它调用一个构造函数并将值赋给变量。 / /这个。foo = new foo ();

在。net 2.0之前,这个。在构造函数完成运行之前,foo可以被分配给foo的新实例。在这种情况下,第二个线程可以进来(在线程1调用Foo的构造函数期间),并经历以下情况:

线程2询问变量是否为空。 / /如果(这一点。Foo == null) 线程2确定变量为非空,因此尝试使用它。 / / this.foo.MakeFoo ()

在. net 2.0之前,您可以声明这一点。Foo是不稳定的来解决这个问题。从。net 2.0开始,您不再需要使用volatile关键字来完成双重检查锁定。

维基百科上有一篇关于双重检查锁定的好文章,简要地提到了这个话题: http://en.wikipedia.org/wiki/Double-checked_locking

只需查看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)和同步的需求。那为了你自己,你自己去找答案吧。

CLR喜欢优化指令,所以当你在代码中访问一个字段时,它可能并不总是访问该字段的当前值(它可能来自堆栈等)。将字段标记为volatile可以确保该指令可以访问该字段的当前值。当程序中的并发线程或操作系统中运行的其他代码可以修改该值(在非锁定场景中)时,这很有用。

您显然失去了一些优化,但它确实使代码更加简单。

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

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