更新:现在使用基于以下评论的只读集合
我相信以下代码应该是线程安全的“锁定免费”代码,但是要确保我没有遗漏某些内容...
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关键字应该足够(以确保不缓存该值),不是吗?有人可以确认一下,或者让我知道我对线程的理解不清楚:)
答案 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和索引器操作的一致性。