线程和简单的死锁治愈

时间:2009-12-12 07:04:36

标签: c++ synchronization deadlock mutex semaphore

使用互斥锁和信号量处理线程(特别是在C ++中)时,有一个简单的经验法则可以避免死锁并具有良好的干净同步效果吗?

9 个答案:

答案 0 :(得分:16)

一个简单的经验法则是始终从应用程序中的任何位置以一致的可预测顺序获取锁。例如,如果您的资源具有名称,请始终按字母顺序锁定它们。如果它们具有数字ID,则始终从最低到最高锁定。确切的顺序或标准是任意的。关键是要保持一致。这样你就永远不会遇到僵局。例如

  1. 线程1锁定资源A
  2. 线程2锁定资源B
  3. 线程1等待获取B
  4. 上的锁定
  5. 线程2等待获取A
  6. 上的锁定
  7. 死锁
  8. 如果您遵循上面概述的经验法则,上述情况永远不会发生。有关更详细的讨论,请参阅Wikipedia entry on the Dining Philosophers problem

答案 1 :(得分:7)

  1. 如果可能的话,设计你的代码,这样你就不必一次锁定多个互斥锁/信号量。
  2. 如果无法做到这一点,请确保始终以相同的顺序锁定多个互斥锁/信号量。因此,如果代码的一部分锁定互斥锁A然后获取信号量B,请确保代码的其他部分不会使用信号量B然后锁定互斥锁A.

答案 2 :(得分:2)

尽量避免获取一个锁并试图获得另一个锁。这可能导致循环依赖并导致死锁。 如果它是不可避免的,那么至少获取锁定的顺序应该是可预测的。

使用RAII(以确保在发生异常时正确释放锁定)

答案 3 :(得分:1)

没有简单的死锁治疗。

以约定的顺序获取锁定:如果所有呼叫都获得A-> B-> C,则不会发生死锁。只有当两个线程之间的锁定顺序不同时才会发生死锁(一个获得A-> B,第二个B-> A)。

实际上很难在内存中的任意对象之间选择一个顺序。在一个简单的琐碎项目是可能的,但在有许多个人贡献者的大型项目上是非常困难的。部分解决方案是通过对锁定进行排名来创建层次结构。模块A中的所有锁定都具有等级1,模块B中的所有锁定具有等级2.当获得等级1的锁定时,可以获得等级2的锁定,但反之亦然。当然,您需要一个围绕锁定原语的框架来跟踪和验证排名。

答案 4 :(得分:0)

阅读Deadlock: the Problem and a Solution.

  

“避免死锁的常见建议是始终以相同的顺序锁定两个互斥锁:如果您始终在互斥锁B之前锁定互斥锁A,那么您将永远不会死锁。有时这很简单,因为互斥锁服务不同目的,但有时候它不是那么简单,例如当互斥体分别保护同一个类的单独实例时。“

答案 5 :(得分:0)

确保其他人谈论的顺序的一种方法是按照其内存地址定义的顺序获取锁。如果在任何时候,您尝试获取应该在序列中较早的锁,则释放所有锁并重新开始。

通过一些工作,可以使用围绕系统原语的一些包装类来自动完成这项工作。

答案 6 :(得分:0)

没有实用治愈。具体来说,没有办法简单地测试代码是否同步正确,或者让程序员遵守绿色V的绅士规则。

没有办法正确测试多线程代码,因为程序逻辑可能依赖于锁获取的时间,因此,执行与执行不同,以某种方式使QA的概念无效。

我会说

  • 更喜欢仅将线程用作多核机器的性能优化
  • 仅在您确定需要此性能时才优化性能
  • 您可以使用线程来简化程序逻辑,但只有在您完全确定自己在做什么时才能使用。要格外小心,所有锁都只限于一小段代码。不要让任何新手接近这样的代码。
  • 永远不要在任务关键型系统中使用线程,例如驾驶飞机或操作危险的机器
  • 在所有情况下,由于更高的调试和QA成本,线程很少具有成本效益

如果您决定执行线程或维护现有代码库:

  • 将所有锁定限制为基本原理
  • 的小而简单的代码片段
  • 避免函数调用或让程序流到锁定下执行的事实不会立即可见。未来作者将更改此功能,无需您的控制即可扩大锁定范围。
  • 获取对象内部的锁以减少锁定范围,使用您自己的线程安全接口包装非线程安全的第三方对象。
  • 在锁定
  • 下执行时永远不会发送同步通知(回调)
  • 只使用RAII锁,在考虑“我们还能从这里退出时”时减少认知负担,如例外情况等等。

关于如何避免多线程的几句话。

单线程设计通常涉及程序组件提供的一些心跳功能,并在循环中调用(称为心跳循环),当调用时,它为所有组件提供了完成下一项工作的机会,并且再次投降控制权。算法主义者在组件内部将其视为“循环”,将转变为状态机,以确定在调用时应该完成的下一步操作。最好将状态维护为各个对象的成员数据。

答案 7 :(得分:0)

有很多简单的“死锁治疗”。但是没有一个易于应用和普遍工作。

最简单的当然是“永远不会有多个线程”。

假设您有一个多线程应用程序,仍然有许多解决方案:

您可以尝试最小化共享状态和同步。两个并行运行且永不交互的线程永远不会死锁。仅当多个线程尝试访问同一资源时才会发生死锁。他们为什么这样做?可以避免吗?可以重构或划分资源,以便例如一个线程可以写入它,而其他线程异步传递它们所需的数据吗?

也许可以复制资源,为每个线程提供自己的私有副本以便使用?

正如所有其他答案所提到的,如果您尝试获取锁定,请按照全局一致的顺序执行此操作。为了简化这一点,您应该尝试确保线程将需要的所有锁定作为单个操作获取。如果一个线程需要获取锁A,B和C,它不应该在不同的时间和不同的地方进行三次lock()调用。你会感到困惑,你将无法跟踪线程所持有的锁,以及它尚未获得哪些锁,然后你就会搞砸订单。如果您可以获得一次所需的所有锁定,那么您可以将其分解为一个单独的函数调用,该调用获取N个锁定,并以正确的顺序执行此操作以避免死锁。

然后还有更雄心勃勃的方法:像CSP这样的技术使得线程非常简单,易于证明是正确的,即使有数千个并发线程也是如此。但它要求你的程序结构与你习惯的结构完全不同。

Transactional Memory是另一个有前途的选择,可能更容易融入传统计划。但是生产质量的实施仍然非常罕见。

答案 8 :(得分:0)

如果你想攻击死锁的可能性,你必须攻击存在死锁的4个关键条件之一。

死锁的4个条件是: 1.互斥 - 一次只有一个线程可以进入临界区。 2.保持并等待 - 即使其他资源不可用,只要他没有完成工作,线程就不会释放他获得的资源。 3.没有抢占 - 线程没有优先于其他线程。 4.资源循环 - 必须有一个循环的线程链,等待来自其他线程的资源。

最容易攻击的条件是资源周期,确保无法循环。