为什么我们这里没有死锁?

Why we don't get a Deadlock here?

在审查自己的代码时,我发现了一段非常有趣的代码,我发誓它会导致死锁,但我已经用多个线程测试了很多次,但没有得到任何结果。

百思不得其解,决定在这里问一下。
所以假设是 LOCK 没有锁定同一个线程,但我想确认一下。
下面这段代码

public class SplitService : ISplitService
{
    private IRecordService recordService;

    public SplitService(IRecordService recordService)
    {
        this.recordService = recordService;
    }

    private ConcurrentQueue<Batch> _batches = new ConcurrentQueue<Batch>();

    public void Feed(Something r)
    {
        lock (this.recordService)
        {
            if (!this.recordService.CanAppend(r))
            {
                Flush();
            }
            this.recordService.Append(r);
        }           
    }

    public void Flush()
    {
        lock (this.recordService)
        {
            if (!this.recordService.Any()) return;

            var record = this.recordService.GetBatch();
            _batches.Enqueue(record);

            this.recordService.Clean();
        }           
    }

    public IEnumerable<Batch> Get()
    {
        while (_batches.Any())
        {
            if (_batches.TryDequeue(out Batch batch))
            {
                yield return batch;
            }
        }
    }
}

如您所见,方法 Feed 锁定到一个 对象 ,如果相同的 returns CanAppend 为假,它调用 Flush 方法也 尝试锁定同一对象 。 所以我预计那里会出现僵局


理解后稍微推断一下,由于Lock是递归的,我们可以假设这也有效:

lock(locker){           
   Console.WriteLine("Hello World");
   await new Task(() => {
        lock(locker){                   
            Console.WriteLine("Hello World from locker");
        }
   });
}

Monitor 对象在 C# 中是递归的,因此您只需要记住在锁定它们的同时解锁它们。例如,这是完全有效的:

lock(someObject)
{
  lock(someObject)
  {
    lock(someObject)
    {
       Consolw.WriteLine("hello world")
    }
  }
}

重要的是要认识到锁只有在您获得锁后才会递归。如果线程 A 已经获取了锁,然后线程 B 尝试获取锁,那么 B 将阻塞,直到线程 A 释放锁。

对于死锁,您需要 2 个访问器和 2 个资源。本例资源只有一个,大家耐心等待。