为什么我们这里没有死锁?
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 个资源。本例资源只有一个,大家耐心等待。
在审查自己的代码时,我发现了一段非常有趣的代码,我发誓它会导致死锁,但我已经用多个线程测试了很多次,但没有得到任何结果。
百思不得其解,决定在这里问一下。
所以假设是 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 个资源。本例资源只有一个,大家耐心等待。