繁体   English   中英

C#无锁编码完整性检查

[英]C# lock free coding sanity check

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

我相信以下代码应该是线程安全的“无锁”代码,但要确保我没有丢失任何东西...

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关键字应该足够了(以确保不缓存该值),不是吗? 有人可以确认一下,还是让我知道我对线程不了解的内容:)

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

此处的最佳做法是使用锁,除非您有非常充分的理由不这样做。 您编写此危险代码的极好的理由是什么?

更新:我的观点是,低锁或无锁代码极具风险,并且世界上只有很少一部分人真正理解它。 让我举一个乔·达菲的例子:

// 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的程序是完全合法的。 你看到了吗? 如果没有,那么您就没有业务进行低锁或无锁编程。 弄错的。

我本人无法弄清楚这些东西。 它伤了我的大脑。 这就是为什么我尝试从不进行与专家分析过的标准实践完全不同的低锁编程的原因。

这取决于意图。 列表的获取/设置是原子的(即使没有volatile)和非缓存的(volatile),但是调用者可以更改列表,这不能保证线程安全。

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

 obj.Data.Add(value);

这里的值很容易被丢弃。

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

我相信这本身是安全的(即使没有volatile), 但是根据其他线程如何使用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和indexer操作的一致性。

我认为,如果只有两个您描述的线程,那么您的代码是正确且安全的。 而且您也不需要那种挥发性,在这里它是没有用的。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM