C#锁定免费编码健全检查

时间:2011-01-17 18:53:56

标签: c# multithreading volatile

更新:现在使用基于以下评论的只读集合

我相信以下代码应该是线程安全的“锁定免费”代码,但是要确保我没有遗漏某些内容...

public class ViewModel : INotifyPropertyChanged
{
   //INotifyPropertyChanged and other boring stuff goes here...

   private volatile List<string> _data;
   public IEnumerable<string> Data
   {
      get { return _data; }
   }

   //this function is called on a timer and runs on a background thread
   private void RefreshData()
   {
      List<string> newData = ACallToAService();
      _data = newData.AsReadOnly();
      OnPropertyChanged("Data"); // yes, this dispatches the to UI thread
   }
}

具体来说,我知道我可以使用lock(_lock)甚至Interlocked.Exchange()但我不相信在这种情况下需要它。 volatile关键字应该足够(以确保不缓存该值),不是吗?有人可以确认一下,或者让我知道我对线程的理解不清楚:)

4 个答案:

答案 0 :(得分:7)

我不知道这是否“安全”;它取决于你所说的“安全”。例如,如果将“安全”定义为“保证从所有线程中观察到所有易失写入的一致排序”,那么您的程序保证在所有硬件上都是“安全的”。

这里的最佳做法是使用锁,除非你有充分的理由不这样做。 您撰写此风险代码的最佳理由是什么?

更新:我的观点是,低锁或无锁代码极其危险并且世界上只有少数人真正了解它。让我给你举个例子,来自Joe Duffy:

// deeply broken, do not use!
class Singleton {
    private static object slock = new object();
    private static Singleton instance;
    private static bool initialized;
    private Singleton() {}
    public Instance {
        get {
            if (!initialized) {
                lock (slock) {
                    if (!initialized) {
                        instance = new Singleton();
                        initialized = true;
                    }
                }
            }
            return instance;
        }
    }
}

此代码已损坏;正确实现C#编译器为您编写一个为该实例返回null的程序是完全合法的。 你能看到的方式吗?如果没有,那么你没有做低锁或无锁编程的业务;你弄错了。

我自己无法弄清楚这些东西;它打破了我的大脑。这就是为什么我试图永远不做低锁编程,这种编程与专家分析的标准实践完全不同。

答案 1 :(得分:5)

这取决于意图是什么。列表的get / set是原子的(即使没有volatile)和非缓存的(volatile),但是调用者可以改变列表,保证线程安全。

还有一种可能会丢失数据的竞争条件:

 obj.Data.Add(value);

这里很容易丢弃价值。

我会使用不可变(只读)集合。

答案 2 :(得分:0)

我认为如果你只有两个你所描述的线程,那么你的代码是正确和安全的。而且你也不需要那种易变性,这里没用。

但请不要将其称为“线程安全”,因为只有您的两个线程以特殊方式使用它才是安全的。

答案 3 :(得分:0)

我认为这本身就是安全的(即使没有易失性),然而可能会出现问题,具体取决于其他线程如何使用Data属性。

前提是您可以保证所有其他线程在对其进行枚举之前读取并缓存Data的值一次(并且不要尝试将其转换为更广泛的接口以执行其他操作),对第二次访问该属性没有一致性假设,那么你应该没问题。如果你不能做出这样的保证(并且很难做出这样的保证,例如,其中一个用户是框架本身通过数据绑定,因此代码是你无法控制的),那么你就不能说这是安全的。

例如,这是安全的:

foreach (var item in x.Data)
{
   // do something with item
}

这是安全的(前提是JIT不允许优化本地,我认为就是这样):

var data = x.Data;
var item1 = FindItem(data, a);
var item2 = FindItem(data, b);
DoSomething(item1, item2);

上述两个可能会对陈旧数据起作用,但它始终是一致的数据。但这不一定是安全的:

var item1 = FindItem(x.Data, a);
var item2 = FindItem(x.Data, b);
DoSomething(item1, item2);

这个可能正在搜索集合的两个不同状态(在某个线程替换之前和之后),因此对每个单独枚举中找到的项目进行操作可能不安全,因为它们可能彼此不一致

更广泛的界面会使问题更严重;例如。如果数据暴露IList<T>,您还必须注意Count和索引器操作的一致性。