锁助手的线程安全使用(关于内存屏障)
我所说的锁定助手指的是一次性对象,可以通过 using
语句来实现锁定。例如,考虑 Jon Skeet 的 MiscUtil:
public class Example
{
private readonly SyncLock _padlock;
public Example()
{
_padlock = new SyncLock();
}
public void ConcurrentMethod()
{
using (_padlock.Lock())
{
// Now own the padlock - do concurrent stuff
}
}
}
现在,考虑以下用法:
var example = new Example();
new Thread(example.ConcurrentMethod).Start();
我的问题是这样的 - 因为 example
是在一个线程上创建的,而 ConcurrentMethod
是在另一个线程上调用的, ConcurrentMethod
的线程不能忽略构造函数中 _padock
的赋值(由于线程缓存/读写重新排序),从而抛出 NullReferenceException
(在 _padLock
本身上)?
我知道使用 Monitor
/lock
进行锁定具有内存屏障的好处,但是当使用诸如此类的锁定助手时,我不明白为什么可以保证这样的屏障。在这种情况下,据我了解,必须修改构造函数:
public Example()
{
_padlock = new SyncLock();
Thread.MemoryBarrier();
}
编辑 Hans Passant 认为线程的创建意味着内存屏障。那么怎么样:
var example = new Example();
ThreadPool.QueueUserWorkItem(s => example.ConcurrentMethod());
现在不一定创建线程......
By lock helpers I am referring to disposable objects with which locking can be implemented via using
statements. For example, consider a typical usage of the SyncLock
class from Jon Skeet's MiscUtil:
public class Example
{
private readonly SyncLock _padlock;
public Example()
{
_padlock = new SyncLock();
}
public void ConcurrentMethod()
{
using (_padlock.Lock())
{
// Now own the padlock - do concurrent stuff
}
}
}
Now, consider the following usage:
var example = new Example();
new Thread(example.ConcurrentMethod).Start();
My question is this - since example
is created on one thread and ConcurrentMethod
is called on another, couldn't ConcurrentMethod
's thread be oblivious to _padock
's assignment in the constructor (due to thread caching / read-write reordering), and thus throw a NullReferenceException
(on _padLock
itself) ?
I know that locking with Monitor
/lock
has the benefit of memory barriers, but when using lock helpers such as these I can't see why such barriers are guaranteed. In that case, as far as I understand, the constructor would have to be modified:
public Example()
{
_padlock = new SyncLock();
Thread.MemoryBarrier();
}
Source: Understanding the Impact of Low-Lock Techniques in Multithreaded Apps
EDIT Hans Passant suggests that the creation of a thread implies a memory barrier. So how about:
var example = new Example();
ThreadPool.QueueUserWorkItem(s => example.ConcurrentMethod());
Now a thread is not necessarily created...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不,您不需要做任何特殊的事情来保证创建内存屏障。这是因为几乎所有用于在另一个线程上执行方法的机制都会在调用线程上产生一个释放栅栏,并在工作线程上产生一个获取栅栏(实际上它们可能是完整的栅栏屏障)。因此,
QueueUserWorkItem
或Thread.Start
都会自动插入必要的屏障。您的代码是安全的。另外,顺便说一句,Thread.Sleep 还会生成内存屏障。这很有趣,因为有些人天真地使用 Thread.Sleep 来模拟线程交错。如果使用此策略来解决低锁代码的问题,那么它可以很好地掩盖您试图查找的问题。
No, you do not need to do anything special to guarentee that memory barriers are created. This is because almost any mechanism used to get a method executing on another thread produces a release-fence barrier on the calling thread and an aquire-fence barrier on the worker thread (actually they may be full fence barriers). So either
QueueUserWorkItem
orThread.Start
will automatically insert the necessary barriers. Your code is safe.Also, as a matter of tangential interest
Thread.Sleep
also generates a memory barrier. This is interesting because some people naively useThread.Sleep
to simulate thread interleaving. If this strategy were used to troubleshoot low-lock code then it could very well mask the problem you were trying to find.