线程和简单的死锁解决方法

发布于 2024-08-14 00:53:21 字数 62 浏览 8 评论 0原文

当使用互斥锁和信号量处理线程(特别是在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

厌倦 2024-08-21 00:53:21

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

  1. 线程 1 锁定资源 A
  2. 线程 2 锁定资源 B
  3. 线程 1 等待获取 B 上的锁
  4. 线程 2 等待获取 A 上的锁 死锁
  5. 如果

遵循上述经验法则,则上述情况永远不会发生。有关更详细的讨论,请参阅关于哲学家就餐问题的维基百科条目

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.

  1. Thread 1 locks resource A
  2. Thread 2 locks resource B
  3. Thread 1 waits to obtain a lock on B
  4. Thread 2 waits to obtain a lock on A
  5. Deadlock

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.

孤单情人 2024-08-21 00:53:21
  1. 如果可能的话,请设计您的代码,以便您永远不必一次锁定多个互斥体/信号量。
  2. 如果这是不可能的,请确保始终以相同的顺序锁定多个互斥体/信号量。因此,如果代码的一部分锁定互斥量 A 然后获取信号量 B,请确保代码的其他部分没有获取信号量 B 然后锁定互斥量 A。
  1. If at all possible, design your code so that you never have to lock more then a single mutex/semaphore at a time.
  2. If that's not possible, make sure to always lock multiple mutex/semaphores in the same order. So if one part of the code locks mutex A and then takes semaphore B, make sure that no other part of the code takes semaphore B and then locks mutex A.
遗心遗梦遗幸福 2024-08-21 00:53:21

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

使用 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)

少女的英雄梦 2024-08-21 00:53:21

没有简单的僵局解决方法。

按约定顺序获取锁:如果所有调用都获取 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.

再浓的妆也掩不了殇 2024-08-21 00:53:21

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

只需做一点工作,就可以使用系统原语周围的一些包装类几乎自动地完成此操作。

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.

只为一人 2024-08-21 00:53:21

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

没有办法正确测试多线程代码,因为程序逻辑可能取决于锁的时序获取,因此每次执行都是不同的,在某种程度上使 QA 的概念失效。

我想说的是,

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

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

  • 限制所有锁定小而简单的代码片段,这些代码对原语进行操作,
  • 避免函数调用或使程序流远离在锁定下执行的事实不能立即可见的地方。此功能将由未来的作者更改,在您无法控制的情况下扩大您的锁定跨度。
  • 在对象内部获取锁以减少锁定范围,使用您自己的线程安全接口包装非线程安全的第三方对象。
  • 在锁定下执行时从不发送同步通知(回调)
  • 仅使用 RAII 锁,以减少思考“我们还能如何从这里退出”时的认知负荷,如异常等。

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

单线程设计通常涉及程序组件提供的一些心跳函数,并在循环中调用(称为心跳周期),当调用时,所有组件都有机会执行下一项工作并交出控制权再次。算法学家喜欢将组件内部的“循环”视为“循环”,这些将变成状态机,以识别调用时下一步应该做什么。状态最好作为各个对象的成员数据来维护。

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

  • prefer using threads only as a performance optimization for multi-core machines
  • only optimize performance when you are sure you need this performance
  • you may use threads to simplify program logic, but only when you are absolutely sure what you are doing. Be extra careful and all locks are confined to a very small piece of code. Do not let any newbies near such code.
  • never use threads in a mission-critical system, such as flying an aircraft or operating dangerous machinery
  • in all cases, threads are seldom cost-effective, due to higher debug and QA costs

If you determined to do threads or maintaining existing codebase:

  • confine all locks to small and simple pieces of code, which operate on primitives
  • avoid function calls or getting the program flow away to where the fact of being executed under lock is not immediately visible. This function will change by future authors, widening your lock span without your control.
  • get locks inside objects to reduce locking scope, wrap non-thread-safe 3rd-party objects with your own thread-safe interfaces.
  • never send synchronous notifications (callbacks) when executing under lock
  • use only RAII locks, to reduce the cognitive load when thinking "how else can we exit from here", as in exceptions, etc.

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.

望喜 2024-08-21 00:53:21

有很多简单的“僵局解决方法”。但没有一个是易于应用且普遍有效的。

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

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

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

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

正如所有其他答案已经提到的那样,如果您尝试获取锁,请按照全局一致的顺序进行操作。为了简化这一点,您应该尝试确保线程需要的所有锁都是作为单个操作获取的。如果一个线程需要获取锁 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.

菩提树下叶撕阳。 2024-08-21 00:53:21

阅读死锁:问题和解决方案。< /a>

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

Read Deadlock: the Problem and a Solution.

"The common advice for avoiding deadlock is to always lock the two mutexes in the same order: if you always lock mutex A before mutex B, then you'll never deadlock. Sometimes this is straightforward, as the mutexes are serving different purposes, but other times it is not so simple, such as when the mutexes are each protecting a separate instance of the same class".

苦笑流年记忆 2024-08-21 00:53:21

如果你想消除死锁的可能性,你必须解决死锁存在的 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文