在C#中访问简单的布尔标志时,是否需要locking或标记为volatile?

让我们只是说你有一个简单的操作在后台线程上运行。 您想要提供取消此操作的方法,以便您创build一个布尔标志,您可以通过取消button的单击事件处理程序将其设置为true。

private bool _cancelled; private void CancelButton_Click(Object sender ClickEventArgs e) { _cancelled = true; } 

现在,您正在从GUI线程设置取消标志,但是您正在从后台线程读取它。 访问布尔之前,你需要locking吗?

你需要这样做(显然locking在button单击事件处理程序):

 while(operationNotComplete) { // Do complex operation lock(_lockObject) { if(_cancelled) { break; } } } 

还是可以接受这样做(没有locking):

 while(!_cancelled & operationNotComplete) { // Do complex operation } 

或者如何将_cancelledvariables标记为volatile。 这是必要的吗?

[我知道有BackgroundWorker类与它内置的CancelAsync()方法,但我对这里的locking和线程variables访问的语义和使用感兴趣,而不是具体的实现,代码只是一个例子。

似乎有两个理论。

1)因为它是一个简单的内置types(并且访问内置types在.net中是primefaces的),并且因为我们只是在一个地方写信给它,只能在后台线程上读取,所以不需要locking或标记为volatile。
2)你应该把它标记为volatile,因为如果你没有编译器可以优化while循环中的读取,因为它认为没有什么能够修改值。

哪一个是正确的技术? (为什么?)

[编辑:这似乎有两个明确界定和反对的思想stream派。 我正在寻找一个明确的答案,所以请尽可能张贴您的理由,并引用您的来源与您的答案。

首先,线程是棘手的; -p

是的,尽pipe所有传言都相反,但是在从多个线程访问bool时, 要么使用lock 或者 volatile (但不能同时使用)。

对于简单的types和访问,如退出标志( bool ),那么volatile就足够了 – 这确保线程不会将值caching在寄存器中(意思是:其中一个线程永远不会看到更新)。

对于更大的值(primefaces性是个问题),或者你想要同步一系列操作(一个典型的例子是“如果不存在并添加”字典访问), lock是更通用的。 这作为一个记忆障碍,所以仍然给你的线程安全,但提供其他function,如脉冲/等待。 请注意,您不应该对值types或string使用lock ; 也不是Typethis ; 最好的select是把你自己的locking对象作为一个字段( readonly object syncLock = new object(); )并locking它。

例如,如果你不同步,它有多坏(例如永远循环) – 请看这里 。

要跨越多个程序,像Mutex*ResetEvent这样的操作系统*ResetEvent也可能是有用的,但这对于单个exe来说是矫枉过正的。

_cancelled必须是volatile 。 (如果你不selectlocking)

如果一个线程更改_cancelled的值,其他线程可能看不到更新的结果。

另外,我认为_cancelled的读/写操作是primefaces的

CLI规范的第12.6.6节规定:“符合CLI的应保证对所有正确alignment的内存位置不超过本地字大小的读写访问是primefaces,当所有的写访问到一个位置是相同的大小。

locking不是必须的,因为你有一个单一的写作者场景,布尔型字段是一个简单的结构,没有破坏状态的风险( 虽然有可能获得既不是假也不是真的布尔值 )。 但是您必须将该字段标记为volatile以防止编译器进行一些优化。 如果没有volatile修饰符,编译器可能会在工作线程上执行循环期间将值caching在寄存器中,并且循环将永远无法识别已更改的值。 此MSDN文章( 如何:创build和终止线程(C#编程指南) )解决此问题。 当需要locking时,locking将与标记该字段具有相同的效果。

对于线程同步,build议您使用其中一个EventWaitHandle类,如ManualResetEvent 。 尽pipe在这里使用一个简单的布尔标志稍微简单一点(当然,您也可以将其标记为volatile ),但IMO最好使用线程工具。 为了你的目的,你会做这样的事情…

 private System.Threading.ManualResetEvent threadStop; void StartThread() { // do your setup // instantiate it unset threadStop = new System.Threading.ManualResetEvent(false); // start the thread } 

在你的线程..

 while(!threadStop.WaitOne(0) && !operationComplete) { // work } 

然后在GUI中取消…

 threadStop.Set(); 

查找Interlocked.Exchange() 。 它将一个非常快速的复制到可用于比较的局部variables中。 它比lock()更快。