线程安全使用锁定助手(关于内存障碍)

时间:2011-07-04 17:21:10

标签: c# multithreading locking memory-barriers

通过锁定助手,我指的是可以通过using语句实现锁定的一次性对象。例如,考虑Jon Skeet's MiscUtilSyncLock类的典型用法:

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();
}

来源:Understanding the Impact of Low-Lock Techniques in Multithreaded Apps

编辑 Hans Passant建议创建一个线程意味着内存障碍。那怎么样:

var example = new Example();
ThreadPool.QueueUserWorkItem(s => example.ConcurrentMethod());

现在不一定要创建一个线程......

1 个答案:

答案 0 :(得分:10)

不,您不需要做任何特别的事情来保证创建内存障碍。这是因为几乎所有用于获取在另一个线程上执行的方法的机制都会在调用线程上生成 release-fence 屏障,并在工作线程上生成 aquire-fence 屏障(实际上他们可能是完全围栏障碍)。因此,QueueUserWorkItemThread.Start会自动插入必要的障碍。您的代码是安全的。

此外,由于切向感兴趣Thread.Sleep也会产生记忆障碍。这很有趣,因为有些人天真地使用Thread.Sleep来模拟线程交错。如果使用此策略对低锁代码进行故障排除,则可以很好地掩盖您尝试查找的问题。