C#/.NET 中不可重置的“标志”线程是不是安全?
Posted
技术标签:
【中文标题】C#/.NET 中不可重置的“标志”线程是不是安全?【英文标题】:Are unresettable "flags" threadsafe in C#/.NET?C#/.NET 中不可重置的“标志”线程是否安全? 【发布时间】:2013-05-09 13:12:56 【问题描述】:(注意:我已经问过这个问题,但答案是针对 Java 的,所以我对 C# 和 .NET 框架提出了同样的问题。它不是重复的。)
我使用这种模式已经有一段时间了,但直到最近我才开始认为这样做可能不太好。基本上,我使用了这种模式的一些变体:
public class SampleAsync
public SampleAsync()
private bool completed;
public void Start()
var worker = new BackgroundWorker();
worker.DoWork += (sender, e) =>
//... do something on a different thread
completed = true;
;
worker.RunWorkerAsync();
public void Update()
if (!completed) return;
//... do something else
*用户负责确保Start
只被调用一次。随时随地调用Update
。
我一直认为这在 C#/.NET 框架中是线程安全的,因为即使没有严格同步,我也只将 completed
设置为 true。一旦观察到它是true
,它就不会重置为false
。它在构造函数中被初始化为 false,根据定义,它是线程安全的(除非你在其中做了一些愚蠢的事情)。那么,以这种方式使用不可重置标志是线程安全的吗? (如果是这样,它甚至会提供任何性能优势吗?)
谢谢
【问题讨论】:
在 C# 和 Java 中?否则它是线程安全的吗? “一旦它被观察到是真实的”是如果你不使用volatile
可能永远不会发生的位。
为什么不直接使用后台worker的IsBusy
属性来判断是否完成呢?我想他们确保可以从任何线程安全地访问它。
@Servy 我并没有真正询问BackgroundWorker
- 这只是为了演示。关键是completed
设置在不同的线程中,不一定在线程生命周期结束时。
@aboveyou00 我的意思是,这就是为什么存在诸如BackgroundWorker
之类的类的原因;帮助管理来自最常见用例的线程之间的同步。你应该学会利用它。
【参考方案1】:
它严重依赖于目标架构。英特尔处理器具有强大的内存模型,因此您倾向于使用这样的代码。但是,抖动很可能会搞砸你。例如,x86 抖动倾向于将变量存储在 cpu 寄存器中,尤其是当 if() 语句出现在紧密循环中时。并且仅在发布版本中这样做,令人敬畏的调试噩梦。声明变量 volatile 是一种创可贴。 x64 抖动不需要,至少在当前版本中是这样。但创可贴往往无法阻止内存模型较弱的处理器(如 ARM 和安腾)的流血。他们当然不保证变量的更新状态很快就会在另一个线程中可见。线程调度程序倾向于刷新 cpu 缓存。最终。
没有正确执行此操作是没有意义的。使用适当的同步对象,例如 AutoResetEvent。如果您担心循环,或者 Interlocked.CompareExchange()。
【讨论】:
volatile 是否保证该字段直接存储并始终从内存中重新加载? (这两个属性都是必需的)。我不清楚 volatile 实际上提供了什么保证。 @usr:您还需要另一个属性——它不是早期编写的或推测性的。 +1 你的最后一段完美地总结了它。您不必询问某些东西是否是线程安全的。使用同步对象并知道。【参考方案2】:您的代码是线程安全的,因为bool
是原子类型。
MSDN:
以下数据类型的读写是原子的:bool、char、 byte、sbyte、short、ushort、uint、int、float 和引用类型。在 此外,读取和写入具有基础类型的枚举类型 前面的列表也是原子的。其他类型的读写, 包括long、ulong、double和decimal,以及用户自定义 类型,不保证是原子的。除了图书馆 为此目的而设计的功能,不能保证原子性 read-modify-write,比如递增或递减的情况。
见:http://msdn.microsoft.com/en-us/library/aa691278(v=vs.71).aspx
请用volatile
标记您的字段:
private volatile bool completed;
MSDN:
volatile关键字表示一个字段可以在 通过诸如操作系统、硬件或 并发执行线程。
见:http://msdn.microsoft.com/en-us/library/x13ttww7(v=vs.71).aspx
【讨论】:
...但如果您使用[FieldOffset]
或[StructLayout]
明确对齐字段,则所有赌注都将失败。见this question。
这是否保证对该字段的写入会传播到其他线程?我不这么认为。该值可能已注册。
对completed
的写入本身可能是原子的且“安全”的,但如果Update
将completed
的值缓存在寄存器中(或做其他具有相同效果的转换)。
@usr:你是对的。这就是程序员应该使用volatile
来防止缓存的原因。
@virtlink:这不是 100% 正确的。所有布尔值True
都是相同的:它们都按位等于 new Int32(1)。否则,当使用 Object.Equals
时,来自 VB.NET 库的 True
将不等于来自 C#.Library 的 True
。我只是尝试了一个具有显式布局的结构,该结构具有一个 bool 和一个 int 相互重叠。所以OP实际上仍在修改一位。但是...如果您是对的,如果布尔值中的任何位被设置为两次写入操作的一部分,Update
方法仍然会收到正确的信号。以上是关于C#/.NET 中不可重置的“标志”线程是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章