C# 无锁编码健全性检查
更新:现在使用基于下面评论的只读集合,
我相信以下代码应该是线程安全的“无锁”代码,但想确保我没有遗漏一些东西......
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 关键字应该足够了(以确保该值不被缓存),不是吗?有人可以确认一下吗,或者让我知道我对线程有什么不明白的地方:)
UPDATED: now using a read-only collection based on comments below
I believe that the following code should be thread safe "lock free" code, but want to make sure I'm not missing something...
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
}
}
Specifically, I know that I could use a lock(_lock)
or even an Interlocked.Exchange()
but I don't believe that there is a need for it in this case. The volatile keyword should be sufficient (to make sure the value isn't cached), no? Can someone please confirm this, or else let me know what I don't understand about threading :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我不知道这是否“安全”;这完全取决于你所说的“安全”是什么意思。例如,如果您将“安全”定义为“保证从所有线程观察到所有易失性写入的一致顺序”,那么您的程序不能保证在所有硬件上都是“安全”的。
这里的最佳实践是使用锁,除非您有充分的理由不这样做。 您编写这段有风险的代码的充分理由是什么?
更新:我的观点是,低锁或无锁代码风险极大,并且仅世界上只有少数人真正理解它。 让我举一个来自 Joe Duffy 的例子:
这段代码已损坏;对于 C# 编译器的正确实现来说,为您编写一个为实例返回 null 的程序是完全合法的。 你能看出怎么做吗?如果没有,那么你就没有必要进行低锁或无锁编程;你会弄错的。
我自己无法弄清楚这些事情;它打破了我的大脑。这就是为什么我尝试永远不进行与专家分析的标准实践有任何背离的低锁编程。
I have no idea whether that is "safe" or not; it depends on precisely what you mean by "safe". For example, if you define "safe" as "a consistent ordering of all volatile writes is guaranteed to be observed from all threads", then your program is not guaranteed to be "safe" on all hardware.
The best practice here is to use a lock unless you have an extremely good reason not to. What is your extremely good reason to write this risky code?
UPDATE: My point is that low-lock or no-lock code is extremely risky and that only a small number of people in the world actually understand it. Let me give you an example, from Joe Duffy:
This code is broken; it is perfectly legal for a correct implementation of the C# compiler to write you a program that returns null for the instance. Can you see how? If not, then you have no business doing low-lock or no-lock programming; you will get it wrong.
I can't figure out this stuff myself; it breaks my brain. That's why I try to never do low-lock programming that departs in any way from standard practices that have been analyzed by experts.
这取决于意图是什么。列表的获取/设置是原子的(即使没有易失性)和非缓存(易失性),但调用者可以改变列表,这不保证线程安全。
还有一种竞争条件可能会丢失数据:
这里的值很容易被丢弃。
我会使用不可变(只读)集合。
It depends on what the intent is. The get/set of the list is atomic (even without volatile) and non-cached (volatile), but callers can mutate the list, which is not guaranteed thread-safe.
There is also a race condition that could lose data:
Here value could easily be discarded.
I would use an immutable (read-only) collection.
我认为,如果您只有两个像您所描述的线程,那么您的代码是正确且安全的。而且你也不需要那么不稳定,它在这里毫无用处。
但请不要称其为“线程安全”,因为它仅对于以特殊方式使用它的两个线程来说是安全的。
I think that if you have only two threads like you described, your code is correct and safe. And also you don't need that volatile, it is useless here.
But please don't call it "thread safe", as it is safe only for your two threads using it your special way.
我相信这本身是安全的(即使没有易失性),但是可能存在问题,具体取决于其他线程如何使用 Data 属性。
假设您可以保证所有其他线程在对其进行枚举之前读取并缓存 Data 的值一次(并且不要尝试将其转换为更广泛的接口来执行其他操作),并且没有对第二次访问该属性的一致性假设,那么你应该没问题。如果您不能做出这样的保证(并且很难做出这样的保证,例如,如果其中一个用户是通过数据绑定的框架本身,因此您无法控制代码),那么您就不能说它是安全的。
例如,这将是安全的:
这将是安全的(前提是不允许 JIT 优化掉本地,我认为就是这种情况):
上面两个可能会过时数据,但它始终是一致的数据。但这不一定是安全的:
这个可能会搜索集合的两种不同状态(在某个线程替换它之前和之后),因此对每个单独的枚举中找到的项目进行操作可能不安全,因为它们可能不安全彼此保持一致。
如果接口更广泛,问题会更严重;例如。如果数据公开
IList
,您还必须注意 Count 和索引器操作的一致性。I believe that this is safe in itself (even without volatile), however there may be issues depending on how other threads use the Data property.
Provided that you can guarantee that all other threads read and cache the value of Data once before doing enumeration on it (and don't try to cast it to some broader interface to perform other operations), and make no consistency assumptions for a second access to the property, then you should be ok. If you can't make that guarantee (and it'd be hard to make that guarantee if eg. one of the users is the framework itself via data-binding, and hence code that you do not control), then you can't say that it's safe.
For example, this would be safe:
And this would be safe (provided that the JIT isn't allowed to optimise away the local, which I think is the case):
The above two might act on stale data, but it will always be consistent data. But this would not necessarily be safe:
This one could possibly be searching two different states of the collection (before and after some thread replaces it), so it may not be safe to operate on items found in each separate enumeration, as they may not be consistent with each other.
The issue would be worse with a broader interface; eg. if Data exposed
IList<T>
you'd have to watch for consistency of Count and indexer operations as well.