c++ - 防止代码死锁的锁定策略和技术

标签 c++ design-patterns locking deadlock

防止代码中死锁的常用解决方案是确保锁定顺序以一种通用方式发生,而不管哪个线程正在访问资源。

例如给定线程 T1 和 T2,其中 T1 访问资源 A,然后 B,T2 访问资源 B,然后 A。按需要的顺序锁定资源会导致死锁。简单的解决方案是先锁定 A,然后再锁定 B,无论特定线程将使用资源的顺序如何。

有问题的情况:

Thread1                         Thread2
-------                         -------
Lock Resource A                 Lock Resource B
 Do Resource A thing...          Do Resource B thing...
Lock Resource B                 Lock Resource A
 Do Resource B thing...          Do Resource A thing...

可能的解决方案:

Thread1                         Thread2
-------                         -------
Lock Resource A                 Lock Resource A
Lock Resource B                 Lock Resource B
 Do Resource A thing...          Do Resource B thing...
 Do Resource B thing...          Do Resource A thing...

我的问题是在编码中使用了哪些其他技术、模式或常见做法来保证防止死锁?

最佳答案

您描述的技术不仅常见:它是一种已被证明一直有效的技术。不过,在使用 C++ 编写线程代码时,您还应该遵循一些其他规则,其中最重要的可能是:

  • 在调用虚函数时不要持有锁:即使在您编写代码时,您知道将调用哪个函数以及它会做什么,代码会不断发展,并且虚函数是要被覆盖的,所以最终,您不会知道它的作用以及它是否会占用任何其他锁,这意味着您将失去保证的锁定顺序
  • 注意竞争条件:在 C++ 中,什么时候给定的数据在线程之间共享并且您没有对其使用某种同步时,没有任何东西可以告诉您。几天前,Luc 在 SO chat 上的 C++ Lounge 中发布了一个示例(本文末尾的代码):只是试图同步某件事else恰好在附近并不意味着您的代码已正确同步。
  • 尝试隐藏异步行为:您通常最好将并发性隐藏在软件架构中,这样大多数调用代码就不会关心那里是否有线程或不。它使架构更易于使用 - 特别是对于不习惯并发的人。

我可以继续讲一段时间,但根据我的经验,最简单使用线程的方法是使用可能使用代码的每个人都知道的模式,例如生产者/消费者模式:很容易解释,你只需要一个工具(队列)就可以让你的线程相互通信。毕竟,两个线程彼此同步的唯一原因是允许它们通信。

更一般的建议:

  • 在您使用锁进行并发编程之前,不要尝试无锁编程 - 这是一种让您大吃一惊或遇到非常奇怪的错误的简单方法。
  • 将共享变量的数量和访问这些变量的次数降至最低。
  • 不要指望两个事件总是以相同的顺序发生,即使您看不到它们以任何方式颠倒顺序。
  • 更笼统地说:不要指望时间 - 不要认为给定的任务应该总是花费给定的时间。

下面的代码会失败:

#include <thread>
#include <cassert>
#include <chrono>
#include <iostream>
#include <mutex>
 
void
nothing_could_possibly_go_wrong()
{
    int flag = 0;
 
    std::condition_variable cond;
    std::mutex mutex;
    int done = 0;
    typedef std::unique_lock<std::mutex> lock;
 
    auto const f = [&]
    {
        if(flag == 0) ++flag;
        lock l(mutex);
        ++done;
        cond.notify_one();
    };
    std::thread threads[2] = {
        std::thread(f),
        std::thread(f)
    };
    threads[0].join();
    threads[1].join();
 
    lock l(mutex);
    cond.wait(l, [done] { return done == 2; });
 
    // surely this can't fail!
    assert( flag == 1 );
}
 
int
main()
{
    for(;;) nothing_could_possibly_go_wrong();
}

关于c++ - 防止代码死锁的锁定策略和技术,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6012640/

相关文章:

c++ - 派生 QMainWindow 并更改其布局

design-patterns - UML 类关系

python - 何时在 python 的线程模块中使用事件/条件/锁/信号量?

c++ - 是否有任何清晰的入门级文档可用于 boost-spirit?

c++ - 我的 C++ 代码可以编译,但变成了无法运行的无法杀死的程序

c++ - 引用变量的一种修改方式

c# - 组织解决方案,需要技巧

java - 练习您的微架构技能

mysql - 如何避免 GORM 中的竞争条件

循环中的 PostgreSQL DROP TABLE 失败,错误为 : out of shared memory