在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
; 也不是Type
或this
; 最好的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()更快。