【发布时间】:2009-12-12 07:04:36
【问题描述】:
在使用互斥锁和信号量处理线程(特别是在 C++ 中)时,是否有一个简单的经验法则可以避免死锁并进行干净的同步?
【问题讨论】:
标签: c++ synchronization deadlock mutex semaphore
在使用互斥锁和信号量处理线程(特别是在 C++ 中)时,是否有一个简单的经验法则可以避免死锁并进行干净的同步?
【问题讨论】:
标签: c++ synchronization deadlock mutex semaphore
一个很好的简单经验法则是始终以一致的可预测顺序从应用程序的任何位置获取锁。例如,如果您的资源有名称,请始终按字母顺序锁定它们。如果他们有数字 id,请始终从最低到最高锁定。确切的顺序或标准是任意的。关键是要保持一致。这样你就永远不会陷入僵局。例如。
如果您遵循上述经验法则,上述情况永远不会发生。如需更详细的讨论,请参阅Wikipedia entry on the Dining Philosophers problem。
【讨论】:
【讨论】:
尽量避免获取一个锁并尝试获取另一个锁。这可能导致循环依赖并导致死锁。 如果这是不可避免的,那么至少获取锁的顺序应该是可预测的。
使用RAII(确保在异常情况下也能正确释放锁)
【讨论】:
没有简单的死锁解决方法。
按约定的顺序获取锁:如果所有调用都获取 A->B->C,则不会发生死锁。仅当两个线程之间的锁定顺序不同(一个获取 A->B,第二个获取 B->A)时,才会发生死锁。
实际上很难在内存中的任意对象之间选择顺序。在一个简单的琐碎项目上是可能的,但在有许多个人贡献者的大型项目上是非常困难的。部分解决方案是通过对锁进行排名来创建层次结构。模块 A 中的所有锁的等级为 1,模块 B 中的所有锁的等级为 2。当持有等级 1 的锁时,可以获得等级为 2 的锁,反之则不行。当然,您需要一个围绕锁定原语的框架来跟踪和验证排名。
【讨论】:
确保其他人讨论过的顺序的一种方法是按照他们的内存地址定义的顺序获取锁。如果在任何时候,您尝试获取本应在序列中较早的锁,则释放所有锁并重新开始。
只需稍做工作,就可以使用围绕系统原语的一些包装类几乎自动完成此操作。
【讨论】:
没有实用的治愈方法。具体来说,没有办法简单地测试代码是否同步正确,或者让你的程序员遵守带绿色 V 的绅士规则。
没有办法正确地测试多线程代码,因为程序逻辑可能依赖于锁获取的时间,因此,执行与执行之间存在差异,从而使 QA 的概念失效。
我会说
如果您决定做线程或维护现有代码库:
关于如何避免多线程的几句话。
单线程设计通常涉及程序组件提供的一些心跳功能,并在循环中调用(称为心跳循环),当被调用时,所有组件都有机会完成下一个工作并再次交出控制权。算法学家喜欢将组件内的“循环”视为组件中的“循环”,它们将变成状态机,以识别调用时应该做的下一件事是什么。状态最好作为各个对象的成员数据进行维护。
【讨论】:
有很多简单的“僵局解决方法”。但是没有一个是容易应用和普遍工作的。
最简单的当然是“永远不会有多个线程”。
假设您有一个多线程应用程序,仍然有许多解决方案:
您可以尝试最小化共享状态和同步。两个并行运行且从不交互的线程永远不会死锁。死锁仅在多个线程尝试访问同一资源时发生。他们为什么这样做?可以避免吗?是否可以对资源进行重组或划分,例如,一个线程可以写入,而其他线程异步传递所需的数据?
也许可以复制资源,让每个线程都有自己的私有副本可以使用?
正如所有其他答案已经提到的那样,如果当您尝试获取锁时,请以全局一致的顺序进行。为了简化这一点,您应该尝试确保线程将需要的所有锁都作为单个操作获取。如果一个线程需要获取锁A、B和C,它不应该在不同的时间和不同的地方进行三个lock()调用。您会感到困惑,并且您将无法跟踪线程持有哪些锁,以及它尚未获取哪些锁,然后您会弄乱顺序。如果你可以一次获取你需要的所有锁,那么你可以将它分解到一个单独的函数调用中,该函数调用获取N个锁,并以正确的顺序执行以避免死锁。
还有更雄心勃勃的方法:CSP 之类的技术使线程变得非常简单,并且很容易证明是正确的,即使有数千个并发线程也是如此。但这需要您以与以往截然不同的方式构建程序。
Transactional Memory 是另一种有前途的选择,并且可能更容易集成到传统程序中。但生产质量的实现仍然非常罕见。
【讨论】:
阅读Deadlock: the Problem and a Solution.
“避免死锁的常见建议是始终以相同的顺序锁定两个互斥锁:如果您始终在互斥锁 B 之前锁定互斥锁 A,那么您将永远不会死锁。有时这很简单,因为互斥锁服务于不同的目的,但其他时候并不那么简单,例如当互斥体各自保护同一类的单独实例时”。
【讨论】:
如果你想攻击死锁的可能性,你必须攻击死锁存在的 4 个关键条件之一。
发生死锁的 4 个条件是: 1. 互斥——一次只能有一个线程进入临界区。 2. Hold and Wait——线程不会释放他获得的资源,只要他没有完成他的工作,即使其他资源不可用。 3. 无抢占 - 一个线程不具有高于其他线程的优先级。 4. 资源循环 - 必须有一个线程循环链等待来自其他线程的资源。
最容易攻击的条件是资源循环,确保没有循环是可能的。
【讨论】: