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

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

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

来源: 了解低锁技术在multithreading应用中的影响

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

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

现在一个线程不一定被创建……

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

此外,作为切线感兴趣的Thread.Sleep也会产生内存屏障。 这很有趣,因为有些人天真地使用Thread.Sleep来模拟线程交错。 如果这个策略用于解锁低锁代码,那么它可以很好地掩盖你试图找到的问题。