事务 - 如何避免死锁?

Posted

技术标签:

【中文标题】事务 - 如何避免死锁?【英文标题】:Transactions - How to avoid deadlocks? 【发布时间】:2009-09-19 06:51:19 【问题描述】:

我在 .net/C# 采访中被问到这个问题:

如果我们有两个线程 T1 和 T2。 T1 获取 obj1 上的锁,然后执行 一些处理并获得锁定 对象2。 T2 在 obj2 上获得锁,并且 然后进行一些处理并获取 obj1 上的锁。所以,我们可以有一个 僵局。什么是常用技术 我们在多线程中使用以避免 这种情况?

我回答说 T1 和 T2 应该有一些通信机制,我们应该以这样一种方式进行编码,即 T2 只有在 T1 发出信号表示它已完成其工作后才开始执行其工作。面试官问我是否知道事务以及我们如何使用它来遇到这种死锁情况。 我在winforms的UI方面有一些多线程经验。但是,我从来没有使用过交易。有人可以告诉我更多关于这个或指向我的网址/书,

【问题讨论】:

【参考方案1】:

避免死锁的一种通用方法是确保您的线程/进程以相同的顺序获取资源锁。例如,T2 应该先锁定 obj1,然后锁定 obj2(与 T1 相同)。

这样你就不能让两个线程都持有另一个线程想要的资源,即死锁。

如果您引用的措辞是确切的问题,那么它写得不好。应该是:

T1 获得了 obj1 的锁,是否 某事,然后尝试也锁定 obj2 未解锁 obj1。 同时 T2 获得 锁定 obj2,做某事并且 然后尝试在不解锁 obj2 的情况下也锁定 obj1。一种 会发生死锁。

我强烈推荐阅读 Joe Duffy 的 Concurrent Programming on Windows。它可能是关于 Windows 线程理论和实践的最全面的书籍。

【讨论】:

@Ash 我明白了以相同顺序获取锁的要点。但是,那么交易是什么? @Sandbox,在我看来,他们似乎在谈论一般意义上的“事务”,即确保一组不同的操作作为一个(即原子地)执行。在数据库中,这是通过 Begin Transaction 关键字实现的,在 .net 中,它是通过(通常)使用 lock 语句来实现的。我在回答中添加了乔·达菲(Joe Duffy)强烈推荐的书。【参考方案2】:

一种方法是所有进程都需要在事务开始时获取所有锁。如果某些不可用,则该进程释放所有锁,然后重试。但是,根据实现,这仍然可能导致活锁。

要从另一端查看问题,请参阅Dijkstra's banker's algorithm。

【讨论】:

【参考方案3】:

我不完全确定事务在哪里直接进入此,除了回滚的能力。 IMO 的主要任务是以一致的顺序和早期获取锁;哦,在获取锁时使用超时 - 不要坐在那里看起来永远卡住了。

对事务的引用让我主要想到了数据库,在这种情况下,另一个考虑因素是使用像 UPDLOCK 这样的技巧来确保您获得写锁最初以避免促进(有争议的)读锁到写锁(从死锁变为简单阻塞)。当然,许多数据库也比大多数常规代码具有更好的死锁检测功能。

【讨论】:

我认为“事务”一词一般意味着确保多个非原子操作作为原子执行。通常的解决方案是使用锁定。【参考方案4】:

我认为,面试官所指的是回滚事务的能力。事务的回滚将还原事务所做的所有更改,就好像事务从未发生过一样。它还会释放它获得的所有锁。

现在让示例中的每个线程使用自己的事务(在数据库、Vista 文件系统或其他支持事务的接口中)。如果发生死锁(一旦发生就可以很容易地检测到),您将从属于死锁(受害者)的线程中选择一个并回滚其事务。这将释放锁,以便剩余的线程可以继续。受害线程可能稍后重试事务。

如果死锁的概率相对于重试整个事务的成本较低,这可能是一个可用的解决方案。

【讨论】:

以上是关于事务 - 如何避免死锁?的主要内容,如果未能解决你的问题,请参考以下文章

在运行三足事务时避免死锁

避免 SQL 事务中的死锁

mysql 开发进阶篇系列 14 锁问题(避免死锁,死锁查看分析)

死锁产生的原因及避免死锁的方法

避免与 Future 阻塞 发生死锁

「操作系统」深入理解死锁(什么是死锁?死锁形成条件?如何避免死锁?如何排查死锁?)