线程和简单的死锁解决方法
当使用互斥锁和信号量处理线程(特别是在 C++ 中)时,是否有一个简单的经验法则可以避免死锁并实现干净的同步?
When dealing with threads (specifically in C++) using mutex locks and semaphores is there a simple rule of thumb to avoid Dead Locks and have nice clean Synchronization?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
一个好的简单的经验法则是始终从应用程序中的任何位置以一致的可预测顺序获取锁。例如,如果您的资源有名称,请始终按字母顺序锁定它们。如果它们有数字 ID,请始终从最低到最高锁定。确切的顺序或标准是任意的。关键是要保持一致。这样你就永远不会陷入僵局。例如。
遵循上述经验法则,则上述情况永远不会发生。有关更详细的讨论,请参阅关于哲学家就餐问题的维基百科条目。
A good simple rule of thumb is to always obtain your locks in a consistent predictable order from everywhere in your application. For example, if your resources have names, always lock them in alphabetical order. If they have numeric ids, always lock from lowest to highest. The exact order or criteria is arbitrary. The key is to be consistent. That way you'll never have a deadlock situation. eg.
The above can never happen if you follow the rule of thumb outlined above. For a more detailed discussion, see the Wikipedia entry on the Dining Philosophers problem.
尽量避免获取一个锁并尝试获取另一个锁。这可能会导致循环依赖并导致死锁。
如果它是不可避免的,那么至少获取锁的顺序应该是可预测的。
使用 RAII (以确保在发生异常时正确释放锁)
Try to avoid acquiring one lock and trying to acquire another. This can result into circular dependency and cause for deadlock.
If it is un-avoidable then at least the order of acquire locks should be predictable.
Use RAII ( to make sure lock is release properly in case of exception as well)
没有简单的僵局解决方法。
按约定顺序获取锁:如果所有调用都获取 A->B->C,则不会发生死锁。仅当两个线程之间的锁定顺序不同时(一个线程获取 A->B,第二个线程获取 B->A),才会发生死锁。
实际上,很难在内存中的任意对象之间选择顺序。在一个简单的琐碎项目上是可能的,但在有许多个人贡献者的大型项目上却非常困难。部分解决方案是通过对锁进行排序来创建层次结构。模块 A 中的所有锁的等级为 1,模块 B 中的所有锁的等级为 2。当持有等级 1 的锁时可以获取等级 2 的锁,但反之则不然。当然,您需要一个围绕锁定原语的框架来跟踪和验证排名。
There is no simple deadlock cure.
Acquire locks in agreed order: If all calls acquire A->B->C then no deadlock can occur. Deadlocks can occur only if the locking order differs between the two threads (one acquires A->B the second B->A).
In practice is hard to choose an order between arbitrary objects in memory. On a simple trivial project is possible, but on large projects with many individual contributors is very hard. A partial solution is to create hierarchies, by ranking the locks. All locks in module A have rank 1, all locks in module B have rank 2. One can acquire a lock of rank 2 when helding locks of rank 1, but not vice-versa. Of course you need a framework around the locking primitives that tracks and validates the ranking.
确保其他人讨论过的顺序的一种方法是按照其内存地址定义的顺序获取锁。如果在任何时候,您尝试获取应该在序列中较早的锁,则释放所有锁并重新开始。
只需做一点工作,就可以使用系统原语周围的一些包装类几乎自动地完成此操作。
One way to ensure the ordering that other folks have talked about is to acquire locks in an order defined by their memory address. If at any point, you try to acquire a lock that should have been earlier in the sequence, you release all the locks and start over.
With a little work, it's possible to do this nearly automatically with some wrapper classes around the system primitives.
没有实际治愈方法。具体来说,没有办法简单地测试代码是否同步正确,或者让程序员遵守绿色V的绅士规则。
没有办法正确测试多线程代码,因为程序逻辑可能取决于锁的时序获取,因此每次执行都是不同的,在某种程度上使 QA 的概念失效。
我想说的是,
如果您决定使用线程或维护现有代码库:
关于如何避免多线程的几句话。
单线程设计通常涉及程序组件提供的一些心跳函数,并在循环中调用(称为心跳周期),当调用时,所有组件都有机会执行下一项工作并交出控制权再次。算法学家喜欢将组件内部的“循环”视为“循环”,这些将变成状态机,以识别调用时下一步应该做什么。状态最好作为各个对象的成员数据来维护。
There's no practical cure. Specifically, there's no way to simply test code for being synchronizationally correct, or to have your programmers obey the rules of the gentleman with the green V.
There's no way to properly test the multithreaded code, because the program logic may depend on timing of locks acquisition, and therefore, be different from execution to execution, somehow invalidating the concept of QA.
I would say
If you determined to do threads or maintaining existing codebase:
A few words on how to avoid multi-threading.
A single-threaded design usually involves some heart-beat function provided by program components, and called in a loop (called heartbeat cycle) which, when called, gives a chance to all components to do the next piece of work and to surrender control back again. What algorithmists like to think of as "loops" inside the components, will turn into state machines, to identify what is the next thing that should be done when called. State is best maintained as member data of respective objects.
有很多简单的“僵局解决方法”。但没有一个是易于应用且普遍有效的。
当然,最简单的是“永远不要有多个线程”。
假设您有一个多线程应用程序,仍然有许多解决方案:
您可以尝试最小化共享状态和同步。两个并行运行且从不交互的线程永远不会发生死锁。仅当多个线程尝试访问同一资源时才会发生死锁。他们为什么这么做?这可以避免吗?是否可以重组或划分资源,以便一个线程可以对其进行写入,而其他线程可以异步传递它们所需的数据?
也许可以复制资源,为每个线程提供自己的私有副本来使用?
正如所有其他答案已经提到的那样,如果您尝试获取锁,请按照全局一致的顺序进行操作。为了简化这一点,您应该尝试确保线程需要的所有锁都是作为单个操作获取的。如果一个线程需要获取锁 A、B 和 C,则它不应该在不同时间、不同位置进行三个
lock()
调用。你会感到困惑,并且无法跟踪线程持有哪些锁,以及尚未获取哪些锁,然后你就会弄乱顺序。如果您可以一次获取所需的所有锁,那么您可以将其分解为一个单独的函数调用,该函数调用获取 N 个锁,并以正确的顺序执行以避免死锁。还有一些更雄心勃勃的方法:像 CSP 这样的技术使线程变得非常简单并且易于证明正确性,即使有数千个并发线程。但它要求您以与您习惯的方式非常不同的方式构建您的程序。
事务内存是另一种有前途的选择,并且可能更容易集成到传统程序中。但达到生产质量的实施仍然非常罕见。
There are plenty of simple "deadlock cures". But none that are easy to apply and work universally.
The simplest of all, of course, is "never have more than one thread".
Assuming you have a multithreaded application though, there are still a number of solutions:
You can try to minimize shared state and synchronization. Two threads that just run in parallel and never interact can never deadlock. Deadlocks only occur when multiple threads try to access the same resource. Why do they do that? Can that be avoided? Can the resource be restructured or divided so that for example, one thread can write to it, and other threads are asynchronously passed the data they need?
Perhaps the resource can be copied, giving each thread its own private copy to work with?
And as already mentioned by every other answer, if and when you try to acquire locks, do so in a global consistent order. To simplify this, you should try to ensure that all the locks a thread is going to need are acquired as a single operation. If a thread needs to acquire locks A, B and C, it should not make three
lock()
calls at different times and from different places. You'll get confused, and you won't be able to keep track of which locks are held by the thread, and which ones it has yet to acquire, and then you'll mess up the order. If you can acquire all the lock you need once, then you can factor it out into a separate function call which acquires N locks, and does so in the correct order to avoid deadlocks.Then there are the more ambitious approaches: Techniques like CSP make threading extremely simple and easy to prove correct, even with thousands of concurrent threads. But it requires you to structure your program very differently from what you're used to.
Transactional Memory is another promising option, and one that may be easier to integrate into conventional programs. But production-quality implementations are still very rare.
阅读死锁:问题和解决方案。< /a>
Read Deadlock: the Problem and a Solution.
如果你想消除死锁的可能性,你必须解决死锁存在的 4 个关键条件之一。
产生死锁的4个条件是:
1. 互斥——一次只有一个线程可以进入临界区。
2. 保持并等待 - 只要线程没有完成其工作,即使其他资源不可用,线程也不会释放其获取的资源。
3. 无抢占 - 线程不具有高于其他线程的优先级。
4. 资源循环——必须有一个线程循环链来等待其他线程的资源。
最容易攻击的条件是资源循环,确保不可能出现循环。
If you want to attack the possibility of a deadlock you must attack one of the 4 crucial conditions for the existence of a deadlock.
The 4 conditions for a deadlock are:
1. Mutual Exclusion - only one thread can enter the critical section at a time.
2. Hold and Wait - a thread doesn't release the resources he acquired as long as he didn't finish his job even if other resources are un available.
3. No preemption - A thread doesn't have a priority over other threads.
4. Resource Cycle - There has to be a cycle chain of threads that waits for resources from other threads.
The easiest condition to attack is the resource cycle by making sure that no cycles are possible.