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

发布于 2024-11-18 04:46:01 字数 1450 浏览 2 评论 0原文

我所说的锁定助手指的是一次性对象,可以通过 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

任谁 2024-11-25 04:46:01

不,您不需要做任何特殊的事情来保证创建内存屏障。这是因为几乎所有用于在另一个线程上执行方法的机制都会在调用线程上产生一个释放栅栏,并在工作线程上产生一个获取栅栏(实际上它们可能是完整的栅栏屏障)。因此,QueueUserWorkItemThread.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 or Thread.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 use Thread.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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文