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

时间:2015-05-26 17:31:58

标签: c# multithreading asynchronous lock-free interlocked

我有一个线程安全类,它使用需要独占访问的特定资源。在我的评估中,让各种方法的调用者在Monitor.Enter上阻塞或等待SemaphoreSlim以访问此资源是没有意义的。

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

更新1:

MyClass在任一方向上使用两个NamedPipesInitBeforeDistribute方法并非真正初始化,而是在两个方向上正确设置连接。在建立连接之前将管道提供给N线程没有意义。一旦设置好,多个线程就可以发布工作,但只有一个线程可以实际读取/写入流。我很抱歉用这些例子命名不好来混淆这一点。

更新2:

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

以下是 good 错误示例:

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. 如果经常调用该方法,可能会有一些性能 好处,当然应该衡量。
  4. 上面的例子在参考文献中没有意义。到(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上的“无锁”代码相关的大多数问题通常都会提出反对意见,并指出它是针对“专家”的。很少(我可能在这个问题上错了)我是否可以看到人们可以深入研究的书籍/博客等等的建议,如果有人这么倾向的话。如果有任何此类资源我应该研究,请分享。任何建议都将受到高度赞赏!

2 个答案:

答案 0 :(得分:1)

更新:很棒的文章

.: Creating High-Performance Locks and Lock-free Code (for .NET) :.

  1. 关于lock-free algorythms的要点并不是experts。{ 重点是Do you really need lock-free algorythm here?我无法理解你的逻辑:

      

    由于初始化多次没有意义,无论是多线程还是单线程,多次调用应立即返回(甚至抛出异常)。

    为什么您的用户不能等待初始化结果,并在此之后使用您的资源?如果可以,只需使用Lazy<T>课程,甚至Asynchronous Lazy Initialization

  2. 您真的应该阅读consensus numberCAS-operations以及为什么在实现您自己的同步原语时这很重要。

    在您的代码中,您使用的是Interlocked.Exchange方法,实际上不是CAS,因为始终会交换该值,并且它的共识数相等到2。这意味着使用此类构造的原语仅适用于2个线程(不是在您的情况下,但仍然是2)。

    我试图定义你的代码是否适用于3个线程,或者某些情况会导致你的应用程序处于损坏状态,但在30分钟后我停止了。在尝试理解您的代码之后,您的团队成员会像我一样停下来。这是浪费时间,不仅是你的,也是你的团队。在真正需要之前,不要重新发明轮子。

  3. 我最喜欢的相关领域的书是Ben Watson的Writing High-Performance .NET Code,我最喜欢的博客是Stephen Cleary。如果您可以更具体地了解您感兴趣的书籍类型,我可以添加更多参考书。

  4. 程序中没有锁定不会使您的应用程序lock-free。在.NET应用程序中,您真的不应该使用Exceptions作为内部程序流。考虑到操作系统暂时没有安排初始化线程(基于各种原因,无论它们究竟是什么)。

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

答案 1 :(得分:0)

Maurice Herlihy和Nir Shavit的多处理器编程艺术,是无锁和无等待编程的绝佳资源。无锁是除了编程模式之外的进步保证,因此,为了证明算法是无锁的,必须验证或显示进度保证的证据。无锁的简单术语意味着阻塞或暂停一个线程不会阻止其他线程的进度,或者如果线程被无限地阻塞,那么还有另一个线程可以无限地取得进展。