c# - 无锁、可等待、独占的访问方式

标签 c# multithreading asynchronous lock-free interlocked

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

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

更新 1:

MyClass 在任一方向使用两个 NamedPipesInitBeforeDistribute 方法并不是真正的初始化,而是正确地建立双向连接。在建立连接之前让 N 线程可以使用管道是没有意义的。设置完成后,多个线程可以发布工作,但实际上只有一个线程可以读/写流。我很抱歉因为示例命名不当而混淆了这一点。

更新 2:

如果 InitBeforeDistribute 实现了具有适当等待逻辑的 SemaphoreSlim(1, 1)(而不是互锁操作抛出异常),Add/Do Square 方法是否正常实践?它不会抛出冗余异常(例如在 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 如果我在哪里使用“适当的”锁。放弃 上面例子的信号量让我不用担心 处理完类(class)后。 (我一直不喜欢 处理多个线程使用的项目的想法。这是一个未成年人 肯定是积极的。)
  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 上的“无锁”代码相关的问题通常都反对它,并声明它是为“专家”准备的。很少(我可能在这一点上是错的)我看到可以深入研究的书籍/博客/等的建议,如果有人愿意的话。如果有任何我应该研究的此类资源,请分享。任何建议将不胜感激!

最佳答案

更新:相关的好文章

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


  1. 关于lock-free的要点算法并不是说它们适用于 experts .
    重点是Do you really need lock-free algorythm here?我在这里无法理解你的逻辑:

    Since it does not make sense to initialize more than once, whether it be from multiple threads or a single one, multiple calls should return immediately (or even throw an exception).

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

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

    在您的代码中,您使用的是 Interlocked.Exchange方法,这不是 CAS实际上,因为它总是交换值(value),并且它的共识数等于2 .这意味着使用这种构造的原语仅适用于 2。线程(不是你的情况,但仍然是 2 )。

    我已尝试确定您的代码是否适用于 3线程,或者可能存在导致您的应用程序损坏状态的某些情况,但在 30 之后分钟我停了下来。在尝试理解您的代码一段时间后,您的任何团队成员都会像我一样停下来。这是在浪费时间,不仅是您的时间,也是您团队的时间。除非真的需要,否则不要重新发明轮子。

  3. 我在相关领域最喜欢的书是 Writing High-Performance .NET Code由 Ben Watson 撰写,我最喜欢的博客是 Stephen Cleary的。如果你能更具体地说明你对什么样的书感兴趣,我可以添加一些更多的引用。

  4. 程序中没有锁不会使您的应用程序成为可能lock-free .在 .NET 应用程序中你真的不应该使用 Exceptions用于您的内部程序流程。考虑到初始化线程没有被操作系统调度一段时间(出于各种原因,无论它们到底是什么)。

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

关于c# - 无锁、可等待、独占的访问方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30465173/

相关文章:

c# - 如何从 Windows Phone 应用程序发送带有代理地址的 HttpWebRequest

c# - 使用 C# 从 MySQL 插入和检索音频文件

c# - .NET Core DI 处理的 NHibernate SessionFactory

c# - NHibernate 没有获取更改

java - 使用 GreenRobot EventBus 在线程之间进行通信

c++ - 池化线程的 SetThreadAffinityMask

c# - 在哪里为基于任务的异步方法定义回调

javascript - then() 在 Promise 之前完成

java - 从具有相同名称的线程中调用父方法

c++ - 如何杀死用C++创建的分离线程