这个示例线程是否安全?

Eri*_*ric 3 .net c# multithreading thread-safety

假设我有一个充当数据缓存的单例类.多个线程将从缓存中读取,并且单个线程将定期刷新它.它看起来像这样:

public sealed class DataStore
{
    public static DataStore Instance { get { return _instance; } }
    public Dictionary<Foo, Bar> FooBar { get; private set; }

    static DataStore() { }
    private DataStore() { }

    public void Refresh() {
        FooBar = GetFooBarFromDB();
    }

    private static readonly DataStore _instance = new DataStore();
}
Run Code Online (Sandbox Code Playgroud)

我的问题基本上是Refresh(),其他线程可能访问时FooBar是否安全?我需要使用锁,还是我的获取和设置操作是原子的?我是否需要显式声明volatile字段来备份我的属性?

PS,如果有人能想到这个问题的更具描述性的标题,我很乐意欢迎它.

编辑:修复了我的例子来明显纠正非原子代码.

das*_*ght 8

是的,在这种情况下,您需要显式同步,因为另一个线程可以FooBar在您完成写入之前获取并开始读取它.

但是,如果你这样做,

public void Refresh() {
    var tmp = new Dictionary<Foo, Bar>();
    // Fill out FooBar from DB
    FooBar = tmp;
}
Run Code Online (Sandbox Code Playgroud)

那么你就不需要添加显式同步,因为从一个引用到另一个引用的切换是原子的.

当然,这里有一个隐含的假设,即在Refresh方法之外没有写作.

编辑:您还应该从自动实现的属性切换到手动实现的属性,其中支持变量使用volatile修饰符声明.

  • 我相信这个解决方案还远远不够.您不应该将普通字典暴露给多个线程.应该只使用`ConcurrentDictionary`或仅通过其接口公开read方法. (3认同)
  • @aqwert你是对的,因为写字方法是通过字典界面公开的,所以解决方案通常不安全.这就是为什么我提到了没有其他作者的隐含假设:没有这个假设,这个解决方案就会破裂.另一个解决方案是使用[来自此答案的readonly包装](http://stackoverflow.com/a/1269311/335858). (3认同)