40

死锁很难找到,而且很难消除。

如何在我的代码中找到死锁的错误源?有没有“死锁模式”?

在我的特殊情况下,它处理数据库,但这个问题对每个死锁都是开放的。

4

12 回答 12

29

更新:这篇最近的 MSDN 文章,识别并发问题的工具和技术,可能也很有趣


Stephen Toub 在 MSDN 文章Deadlock monitor中指出了发生死锁所必需的以下四个条件:

  • 有限数量的特定资源。对于 C# 中的监视器(使用 lock 关键字时使用的监视器),这个有限的数量是 1,因为监视器是互斥锁(意味着一次只有一个线程可以拥有一个监视器)。

  • 持有一种资源并请求另一种资源的能力。在 C# 中,这类似于锁定一个对象,然后在释放第一个锁定之前锁定另一个对象,例如:


lock(a)
{
...
    lock(b)
    {
            ...
    }
}
  • 没有抢占能力。在 C# 中,这意味着一个线程不能强制另一个线程释放锁。

  • 循环等待条件。这意味着存在一个线程循环,每个线程都在等待下一个释放资源才能继续。

他继续解释说,避免死锁的方法是避免(或阻止)条件四。

Joe Duffy 讨论了几种 避免和检测死锁的技术,包括一种称为锁平衡的技术。在锁平衡中,锁被分配了数值,并且线程必须只获取比它们已经获取的锁具有更高数字的锁。这防止了循环的可能性。在当今的典型软件应用程序中,通常也很难做好,并且未能在每次锁定获取时遵循锁定平衡会导致死锁。

于 2009-02-09T14:28:56.600 回答
15

经典的死锁场景是 A 持有锁 X 并想要获取锁 Y,而 B 持有锁 Y 并想要获取锁 X。由于双方都无法完成他们尝试做的事情,因此双方最终都会永远等待(除非超时)用过的)。

在这种情况下,如果 A 和 B 以相同的顺序获取锁,则可以避免死锁。

于 2009-02-09T14:25:31.193 回答
8

确保所有事务以相同的顺序影响表是避免最常见死锁的关键。

例如:

交易A

UPDATE Table A SET Foo = 'Bar'
UPDATE Table B SET Bar = 'Foo'

交易 B

UPDATE Table B SET Bar = 'Foo'
UPDATE Table A SET Foo = 'Bar'

这极有可能导致死锁,因为事务 A在表 A 上获得了锁,事务 B在表 B 上获得了锁,因此在另一个命令完成之前,他们都没有获得第二个命令的锁。

所有其他形式的死锁通常是由高强度使用和 SQL Server 在内部分配资源时死锁引起的。

于 2009-02-09T14:35:53.243 回答
8

据我所知,没有死锁模式(以及 12 年编写大量多线程交易应用程序)。但是 TimedLock 类在发现代码中存在的死锁方面有很大帮助,而无需大量返工。

http://www.randomtree.org/eric/techblog/archives/2004/10/multithreading_is_hard.html

基本上,(在 dotnet/c# 中)你用“使用 TimedLock.Lock(xxx)”搜索/替换所有“lock(xxx)”语句

如果检测到死锁(在指定的超时时间内无法获得锁,默认为 10 秒),则抛出异常。我的本地版本也立即记录堆栈跟踪。走上堆栈跟踪(最好使用行号调试构建),您将立即看到在故障点持有哪些锁,以及它试图获取哪个锁。

在 dotnet 1.1 中,在所描述的死锁情况下,幸运的是,所有被锁定的线程都会同时抛出异常。所以你会得到 2+ 堆栈跟踪,以及解决问题所需的所有信息。(2.0+ 可能已经在内部改变了线程模型,以至于没有这么幸运,我不确定)

于 2009-02-09T14:40:47.010 回答
4

是的 - 当进程尝试以随机顺序获取资源时会发生死锁。如果您的所有进程都试图以相同的顺序获取相同的资源,那么即使没有消除死锁的可能性也会大大降低。

当然,这并不总是那么容易安排...

于 2009-02-09T14:23:49.357 回答
2

我推荐阅读Herb Sutter的这篇文章。它解释了死锁问题背后的原因,并在本文中提出了一个框架来解决这个问题。

于 2009-06-22T14:54:41.007 回答
1

典型的场景是不匹配的更新计划(表并不总是以相同的顺序更新)。然而,在高处理量下发生死锁并不罕见。

我倾向于接受死锁作为生活中的事实,它总有一天会发生,所以我让我的 DAL 准备好处理并重试死锁操作。

于 2009-02-09T14:22:31.110 回答
1

最常见的(根据我的不科学观察)数据库死锁场景非常简单:

  • 两个进程读取某些内容(例如 DB 记录),都在关联资源(通常是 DB 页)上获取共享锁,
  • 两者都试图进行更新,试图将他们的锁升级为独占锁——瞧,死锁。

如果要在读取之后进行更新,则可以通过指定“FOR UPDATE”子句(或类似的,取决于您的特定 RDBMS)来避免这种情况。这样,进程从一开始就获得了排他锁,使得上述情况变得不可能。

于 2009-02-09T14:42:22.560 回答
1

当两个进程都在等待另一个进程完成之前发生的情况。结果是两个进程都挂起。它最常见的多任务处理和 clint/server。

于 2011-11-12T12:55:08.457 回答
0

死锁主要发生在存在多个依赖锁时。在一个线程和另一个线程试图以相反的顺序锁定互斥锁时发生。应该注意使用互斥锁以避免死锁。

务必在解除锁定后完成操作。如果你有多个锁,比如访问顺序是ABC,释放顺序也应该是ABC。

于 2013-11-08T02:43:24.430 回答
0

在我的上一个项目中,我遇到了 sql Server 数据库中的死锁问题。找到原因的问题是,我的软件和第三方软件正在使用同一个数据库并且在同一个表上工作。很难找出导致死锁的原因。我最终编写了一个 sql-query 来找出哪些进程以及哪些 sql-Statements 导致了死锁。您可以在此处找到该语句:SQL-Server 上的死锁

于 2014-02-12T16:55:00.020 回答
0

为了避免死锁,有一种称为银行家算法的算法

这也提供了有用的信息,以避免死锁。

于 2015-11-09T13:47:36.163 回答