Sim*_*ens 43 .net c# multithreading locking thread-safety
让我们说你有一个在后台线程上运行的简单操作.您希望提供一种取消此操作的方法,以便创建一个布尔标志,您可以从取消按钮的单击事件处理程序设置为true.
private bool _cancelled;
private void CancelButton_Click(Object sender ClickEventArgs e)
{
_cancelled = true;
}
Run Code Online (Sandbox Code Playgroud)
现在您正在从GUI线程设置取消标志,但是您正在从后台线程中读取它.在访问bool之前你需要锁定吗?
你需要这样做(显然也锁定了按钮点击事件处理程序):
while(operationNotComplete)
{
// Do complex operation
lock(_lockObject)
{
if(_cancelled)
{
break;
}
}
}
Run Code Online (Sandbox Code Playgroud)
或者这样做是否可以接受(没有锁定):
while(!_cancelled & operationNotComplete)
{
// Do complex operation
}
Run Code Online (Sandbox Code Playgroud)
或者将_cancelled变量标记为volatile.这有必要吗?
[我知道BackgroundWorker类有内置的CancelAsync()方法,但是我对这里的语义和锁定和线程变量访问的使用感兴趣,而不是具体的实现,代码只是一个例子.
似乎有两种理论.
1)因为它是一个简单的内置类型(并且对内置类型的访问在.net中是原子的)并且因为我们只在一个地方写入它并且只在后台线程上读取而不需要锁定或标记为volatile.
2)你应该将它标记为volatile,因为如果你不这样做,编译器可能会优化while循环中的读取,因为它认为它无法修改该值.
哪种方法正确?(为什么?)
[编辑:在这方面似乎有两个明确定义和对立的思想流派.我正在寻找一个明确的答案,所以请尽可能发布您的理由并引用您的消息来源和您的答案.]
Mar*_*ell 39
首先,线程很棘手;-p
是的,尽管所有的谣言相反,它被要求要么使用lock
或 volatile
(但不能同时)访问时,bool
从多个线程.
对于简单类型和访问,例如exit flag(bool
),volatile
这就足够了 - 这可以确保线程不会将值缓存在它们的寄存器中(意思是:其中一个线程永远不会看到更新).
对于较大的值(原子性是一个问题),或者您想要同步一系列操作的地方(典型示例是"如果不存在并添加"字典访问),则a lock
更通用.这充当了内存屏障,因此仍然为您提供线程安全性,但提供其他功能,如脉冲/等待.请注意,您不应lock
在值类型或a上使用string
; 也不Type
或this
; 最好的选择是将您自己的锁定对象作为字段(readonly object syncLock = new object();
)并锁定它.
例如,如果你不同步,它会有多么糟糕(即永远循环) - 请参阅此处.
为了跨越多个程序,OS原语就像Mutex
或者*ResetEvent
也可能有用,但这对于单个exe来说是过度的.
_cancelled
一定是volatile
.(如果你不选择锁定)
如果一个线程更改了值_cancelled
,则其他线程可能看不到更新的结果.
另外,我认为读/写操作_cancelled
是原子的:
CLI规范的第12.6.6节规定:"当一个位置的所有写访问都是相同大小时,符合要求的CLI应保证对不大于本机字大小的正确对齐的内存位置的读写访问权限是原子的."
锁定不是必需的,因为你有一个编写器场景,布尔字段是一个简单的结构,没有破坏状态的风险(虽然有可能得到一个既不假也不是真的布尔值).但是你必须标记该字段volatile
以防止编译器进行一些优化.如果没有volatile
修饰符,编译器可以在您的工作线程上执行循环期间将值缓存在寄存器中,反过来循环将永远不会识别更改的值.这篇MSDN文章(如何:创建和终止线程(C#编程指南))解决了这个问题.虽然需要锁定,但锁具有与标记字段相同的效果volatile
.
归档时间: |
|
查看次数: |
7157 次 |
最近记录: |