是否可以在C#中创建死锁,除了在原始数据访问周围使用lock关键字之外什么都没有?

Con*_*ngo 2 .net c# multithreading thread-safety threadpool

我已经编写了很多多线程C#代码,并且我从未在我发布的任何代码中遇到过死锁.

我使用以下经验法则:

  1. 我倾向于只使用lock关键字(我也使用其他技术,如读取器/写入器锁,但很少,只有在速度需要时).
  2. 我使用,Interlocked.Increment如果我正在处理long.
  3. 我倾向于使用最小的粒度锁定单元:我只倾向于锁定原始数据结构,例如long,dictionarylist.

我想知道如果这些规则是拇指一直遵循,甚至可能产生死锁,如果是这样,代码会是什么样子?

更新

我也使用这些经验法则:

  1. 避免在可能无限期暂停的任何事物周围添加锁定,尤其是I/O操作.如果您必须这样做,请确保锁定内的所有内容在设置后都会超时TimeSpan.
  2. 我使用的锁定对象始终专用的对象,如object _lockDict = new object();然后lock(_lockDict) { // Access dictionary here }.

更新

Jon Skeet的精彩回答.它还证实了为什么我永远不会遇到死锁,因为我倾向于本能地避免嵌套锁,即使我使用它们,我总是本能地保持入口顺序一致.

并且在回应我关于倾向于仅使用lock关键字的评论时,即使用Dictionary+ lock代替ConcurrentDictionary,Jon Skeet发表了这样的评论:

@Contango:这正是我采取的方法.我会去寻找简单的代码,每次锁定"聪明的"无锁代码,直到有证据表明它引起了问题.

Jon*_*eet 7

是的,它很容易死锁,没有实际访问任何数据:

private readonly object lock1 = new object();
private readonly object lock2 = new object();

public void Method1()
{
    lock(lock1)
    {
        Thread.Sleep(1000);
        lock(lock2)
        {
        }
    }
}

public void Method2()
{
    lock(lock2)
    {
        Thread.Sleep(1000);
        lock(lock1)
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通话双方Method1Method2在大致相同的时间,和繁荣-死锁.每个线程都将等待"内部"锁定,另一个线程已将其作为其"外部"锁定获取.

如果你确保你总是以相同的顺序获得锁(例如" lock2除非你已经拥有,否则永远不会获得" lock1)并以相反的顺序释放锁(如果你正在获取/释放,这是隐含的lock)那么你就不会得到一种僵局.

可以仍然得到与异步代码僵局,在与有关单个线程-但涉及Task,以及:

public async Task FooAsync()
{
    BarAsync().Wait(); // Don't do this!
}

public async Task BarAsync()
{
    await Task.Delay(1000);
}
Run Code Online (Sandbox Code Playgroud)

如果你从WinForms线程运行该代码,你将在一个线程中死锁 - FooAsync将阻塞返回的任务BarAsync,并且BarAsync由于等待返回到UI线程而无法继续运行.基本上,您不应该从UI线程发出阻塞调用...