繁体   English   中英

无锁,等待,独占访问方法

[英]Lock-free, awaitable, exclusive access methods

我有一个线程安全类,该类使用需要专门访问的特定资源。 以我的评估,在Monitor.Enter上阻止各种方法的调用者是没有意义的。输入或等待SemaphoreSlim以访问此资源。

例如,我有一些“昂贵的”异步初始化。 由于多次初始化(从多个线程还是单个线程)没有意义,因此多个调用应立即返回(甚至引发异常)。 相反,应该创建,初始化然后将实例分发到多个线程。

更新1:

MyClass在两个方向上都使用两个NamedPipes InitBeforeDistribute方法并不是真正的初始化,而是正确地在两个方向上建立连接。 在建立连接之前,使管道可用于N线程是没有意义的。 设置完成后,多个线程可以发布工作,但是实际上只有一个线程可以读取/写入流。 我为这些示例的命名不合理而感到困惑。

更新2:

如果InitBeforeDistribute使用适当的等待逻辑实现了SemaphoreSlim(1, 1) (而不是互锁的操作抛出异常),那么Add / Do Square方法是否可行? 它没有锁时不会抛出冗余异常(例如InitBeforeDistribute中的InitBeforeDistribute )吗?

以下将是 不好的例子:

class MyClass
{
    private int m_isIniting = 0; // exclusive access "lock"
    private volatile bool vm_isInited = false; // vol. because other methods will read it

    public async Task InitBeforeDistribute()
    {
        if (Interlocked.Exchange(ref this.m_isIniting, -1) != 0)
            throw new InvalidOperationException(
                "Cannot init concurrently! Did you distribute before init was finished?");

        try
        {
            if (this.vm_isInited)
                return;

            await Task.Delay(5000)      // init asynchronously
                .ConfigureAwait(false);

            this.vm_isInited = true;
        }
        finally
        {
            Interlocked.Exchange(ref this.m_isConnecting, 0);
        }
    }
}

一些要点:

  1. 如果存在阻塞/等待访问锁的情况很合理的情况,则此示例没有意义(也就是说)。
  2. 由于我需要在该方法中等待,因此如果要在其中使用“适当”的锁,则必须使用SemaphoreSlim之类的东西。 放弃上面示例的信号量后,我不必担心一旦完成处理该类的工作。 (我总是不喜欢处理多个线程使用的项目的想法。当然,这是次要的肯定。)
  3. 如果经常调用该方法,则可能会带来一些性能上的好处,当然应该对其进行衡量。

上面的示例在参考文献中没有任何意义。 至(3.),因此这是另一个示例:

class MyClass
{
    private volatile bool vm_isInited = false; // see above example
    private int m_isWorking = 0; // exclusive access "lock"
    private readonly ConcurrentQueue<Tuple<int, TaskCompletionSource<int>> m_squareWork =
        new ConcurrentQueue<Tuple<int, TaskCompletionSource<int>>();

    public Task<int> AddSquare(int number)
    {
        if (!this.vm_isInited) // see above example
            throw new InvalidOperationException(
                "You forgot to init! Did you already distribute?");

        var work = new Tuple<int, TaskCompletionSource<int>(number, new TaskCompletionSource<int>()
        this.m_squareWork.Enqueue(work);

        Task do = DoSquare();

        return work.Item2.Task;
    }

    private async Task DoSquare()
    {
        if (Interlocked.Exchange(ref this.m_isWorking, -1) != 0)
            return; // let someone else do the work for you

        do
        {
            try
            {
                Tuple<int, TaskCompletionSource<int> work;

                while (this.m_squareWork.TryDequeue(out work))
                {
                    await Task.Delay(5000)      // Limiting resource that can only be
                        .ConfigureAwait(false); // used by one thread at a time.

                    work.Item2.TrySetResult(work.Item1 * work.Item1);
                }
            }
            finally
            {
                Interlocked.Exchange(ref this.m_isWorking, 0);
            }
        } while (this.m_squareWork.Count != 0 &&
            Interlocked.Exchange(ref this.m_isWorking, -1) == 0)
    }
}

我应该注意这个“无锁”示例的某些特定负面影响吗?

关于SO上的“无锁”代码的大多数问题通常都建议不要这样做,并指出这是给“专家”的。 很少(我可能在这本书上写错了),如果有人倾向于的话,我是否能看到有关书籍/博客/等方面的建议? 如果有任何此类资源可供参考,请分享。 任何建议将不胜感激!

更新:相关的好文章

。:创建高性能锁和无锁代码(用于.NET):。


  1. 关于无lock-free要点不是针对experts
    要点是Do you really need lock-free algorythm here? 我在这里无法理解您的逻辑:

    由于多次初始化(从多个线程还是单个线程)没有意义,因此多个调用应立即返回 (甚至引发异常)。

    为什么您的用户不能简单地等待初始化结果,然后再使用您的资源? 如果可以的话,只需使用Lazy<T>类,甚至使用Asynchronous Lazy Initialization

  2. 您确实应该阅读有关共识号CAS运算的知识,以及在实现自己的同步原语时为何如此重要。

    在您的代码中,您使用的是Interlocked.Exchange方法,该方法不是真正的CAS ,因为它始终交换值,并且其共识数等于2 这意味着使用这种构造的原语只能在2线程中正常工作(不是您的情况,而是2 )。

    我试图定义是您的代码在3线程中正常工作,还是可能在某些情况下导致您的应用程序进入损坏状态,但是30分钟后我停了下来。 经过一段时间的努力,您的任何团队成员都会像我一样停下来理解您的代码。 这不仅浪费时间,而且浪费您的团队。 除非确实需要,否则不要重新发明轮子。

  3. 我在相关领域中最喜欢的书是Ben Watson 编写的高性能.NET代码 ,而我最喜欢的博客是Stephen Cleary的博客。 如果您可以更具体地了解自己感兴趣的书籍,则可以添加更多参考资料。

  4. 程序中没有锁不会使您的应用程序无lock-free 在.NET应用程序中,实际上不应在内部程序流中使用Exceptions 考虑一下操作系统并没有安排初始化线程一段时间(出于各种原因,无论它们到底是什么)。

    在这种情况下,应用程序中的所有其他线程将逐步尝试访问共享资源而死亡。 我不能说这是无lock-free代码。 是的,其中没有锁,但是不能保证程序的正确性,因此按定义它不是无锁的

Maurice Herlihy和Nir Shavit撰写的《多处理器编程的艺术》是无锁和无等待编程的重要资源。 无锁是除编程模式以外的一种进度保证,因此要说一种算法是无锁的,则必须验证或显示该进度保证的证明。 简单地说,无锁意味着阻塞或停止一个线程不会阻塞其他线程的进度,或者如果一个线程被无限频繁地阻塞,那么还有另一个线程会无限地频繁进步。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM