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 中发布了一个这样的例子(本文末尾的代码):just trying to synchronize on something 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/40017127/

相关文章:

java - Controller属于Presentation层?

c++ - 是否有一个 double 值,尽管对其应用了任何计算,它仍将保留其自身?

C++检查文件是否存在而不打开它?

design-patterns - 口齿不清 : dynamic scope vs explicit parameter passing

php - 锁定表后,我收到警告

c# - 为什么需要冗余锁对象?

java - 有什么方法可以在java中锁定(同步)二维数组中的一行?

c++ - C++ 中不同类的方法映射

c++ - 返回指向类成员的指针——我应该避免它吗?

java - 关闭和访客模式之间有显着差异吗?