【问题标题】:Why the lock inside AsyncLock does not block the thread?为什么 AsyncLock 里面的锁不会阻塞线程?
【发布时间】:2019-07-20 08:02:34
【问题描述】:

我正在尝试了解 AsyncLock 的工作原理。

首先,这里有一个sn-p来证明它确实有效:

var l = new AsyncLock();
var tasks = new List<Task>();
while (true)
{
    Console.ReadLine();
    var i = tasks.Count + 1;
    tasks.Add(Task.Run(async () =>
    {
        Console.WriteLine($"[{i}] Acquiring lock ...");
        using (await l.LockAsync())
        {
            Console.WriteLine($"[{i}] Lock acquired");
            await Task.Delay(-1);
        }
    }));
}

“工作”是指您可以运行任意数量的任务(按 Enter 键)并且线程数不会增加。如果您将其替换为传统的lock,您会看到新线程已启动,这是我们尽量避免的。

但是您在源代码中看到的第一件事是...lock

有人可以解释一下这是如何工作的,为什么它不会阻塞,以及我在这里遗漏了什么?

【问题讨论】:

  • 这个问题有点不清楚,上面的代码行不行,能不能展示下行得通的代码
  • 是的,它有效,但我不明白为什么。
  • “作品”是指它重用线程。但是lock 关键字应该阻塞线程(在同一个例子中很容易证明这一点)。
  • "re-uses threads" 无关紧要,Task 不等于线程。然而,更准确地说,“lock语句获取给定对象的互斥锁,执行语句块,然后释放锁。当持有锁时,持有锁的线程可以再次获取和释放锁。任何其他线程都被阻止获取锁并等待直到锁被释放。"
  • 是的,任务被映射到线程,但是由于它们都试图获取相同的锁,所有调度程序的线程都应该被阻塞,每个新任务应该在延迟后启动一个新线程。我刚刚尝试过,对于简单的锁定操作来说确实如此。但由于某种原因,AsyncLock 内部的锁表现不同,或者我错过了一些东西。

标签: c# asynchronous async-await locking task-parallel-library


【解决方案1】:

有人可以解释一下这是如何工作的,为什么它不会阻塞,以及我在这里遗漏了什么?

简短的回答是lock 只是一种用于保证线程安全的内部机制。 lock 永远不会以任何方式公开,并且任何线程都无法在任何实际时间内持有该锁。这样,就类似于各种并发集合内部使用的锁。

还有一种使用无锁编程的替代方法,但我发现无锁编程极难编写、读取和维护。这方面的一个很好的例子(遗憾的是,它不在网上)是 Dobb 博士在 90 年代后期的一堆文章,每篇文章都试图通过更好的无锁队列实现来超越最后一篇文章。事实证明,它们都是错误的——在某些情况下,这些错误需要十多年才能找到。

对于我自己的代码,我不使用无锁编程,除非代码的正确性非常明显。


至于异步锁与锁的概念,我将尝试解释这一点。我有一种感觉,我只有在使用异步协调原语时才有这种感觉。关于写一篇博客文章,我已经考虑了很多,但我没有合适的词来使它易于理解。话虽如此,这就是……

异步协调原语存在于与正常协调原语完全不同的平面上。同步原语阻塞线程和信号线程。异步原语只适用于普通对象;阻塞或信号只是“按照惯例”。

因此,对于普通的lock,调用代码必须立即获取锁。但是对于异步“锁”,尝试的锁只是一个请求,只是一个对象。调用代码甚至不需要await 它。可以请求几个 锁和await 它们与Task.WhenAll 一起。甚至将它们与其他事物结合起来;代码可以做一些疯狂的事情,例如(a)等待两个锁都空闲等待发送信号(如AsyncManualResetEvent),然后如果信号首先出现则取消锁定请求.

从线程的角度来看,它有点像用户模式线程调度。与协作式多任务处理(而不是抢占式)也有一些相似之处。但总的来说,异步原语被“提升”到了一个不同的平面,其中一个只适用于对象和代码块,而不是线程。

【讨论】:

    【解决方案2】:

    lock inside AsyncLock 正在快速发布。每个尝试获取AsyncLock 的任务都成功获取了它的内部lock,并且实际的锁定逻辑是通过队列完成的。

    【讨论】:

      【解决方案3】:

      通过将LockAsync() 包装在using 块中,锁定将在块结束时被释放,因为LockAsync 返回一个一次性对象Key,该对象将被放置在using 块的末尾,然后处置锁将被释放。见https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Coordination/AsyncLock.cs#L182-L185

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-19
        • 2016-06-26
        • 2021-08-29
        • 2011-07-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多