【C++多线程编程】(九)之 死锁(deadlock)及如何避免

死锁(Deadlock)是多线程或多进程并发编程中一种常见的问题,它发生在两个或多个线程(或进程)互相等待对方释放资源,导致所有参与的线程都无法继续执行的状态。

典型的死锁场景涉及多个资源和多个线程,并且每个线程都在等待其他线程释放资源。死锁的产生通常包括以下四个必要条件,也称为死锁的四个必要条件:

  1. 互斥条件(Mutual Exclusion): 某个资源一次只能被一个线程(或进程)占用,其他线程必须等待释放。
  2. 持有和等待条件(Hold and Wait): 某个线程(或进程)持有至少一个资源,并在等待获取其他资源。
  3. 不可抢占条件(No Preemption): 资源不能被抢占,只能在持有资源的线程(或进程)释放后才能被其他线程获取。
  4. 循环等待条件(Circular Wait): 存在一个等待循环,即一组线程(或进程)互相等待,形成一个循环。

当这四个条件同时满足时,就有可能导致死锁的发生。死锁是并发编程中的一种非常棘手的问题,因为它不仅会导致程序无法继续执行,还很难被检测和解决。

避免死锁的一些常用策略包括:

  • 破坏互斥条件: 允许多个线程(或进程)共享资源。
  • 破坏持有和等待条件: 一次性获取所有需要的资源,或者在没有足够资源时释放已经持有的资源。
  • 破坏不可抢占条件: 允许抢占资源。
  • 破坏循环等待条件: 给资源编号,线程(或进程)按顺序请求资源,避免循环等待。

死锁是一个需要仔细设计和管理的问题,因此在编写多线程或多进程的程序时,需要谨慎地考虑资源的获取和释放顺序,以及采取适当的同步机制来避免死锁的发生。

这个程序演示了如何使用互斥量和独占锁来保护共享资源(num_things),以确保在多线程环境中对这些资源的访问是安全的。使用 std::unique_lockstd::defer_lock 参数来延迟锁定,以便稍后手动调用 std::lock 同时锁定两个互斥量,从而避免死锁的发生。

#include <mutex>
#include <thread>
#include <iostream>

// 定义一个包含数量信息和互斥量的结构体 Box
struct Box {
   
    explicit Box(int num) : num_things{
   num} {
   }
    int num_things;
    std::mutex m;
};

// 定义一个转账函数,将一定数量的东西从一个 Box 转移到另一个 Box
void transfer(Box &from, Box &to, int num)
{
   
    // 使用 std::unique_lock 来管理互斥量,但不立即上锁(defer_lock)
    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);

    // 同时锁定两个互斥量,以避免死锁
    std::lock(lock1, lock2);  // 或者使用 lock1.lock() 和 lock2.lock()

    // 在临界区内进行操作,从一个 Box 中减去一定数量,同时将这些东西加到另一个 Box
    from.num_things -= num;
    to.num_things += num;

    // 作用域结束时,std::unique_lock 的析构函数会自动解锁互斥量
}

int main()
{
   
    // 创建两个 Box 对象,分别初始化数量
    Box acc1(100);
    Box acc2(50);

    // 创建两个线程,分别执行 transfer 函数进行转账操作
    std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
    std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);

    // 等待两个线程执行结束
    t1.join();
    t2.join();

    // 输出两个 Box 的最终数量
    std::cout << "acc1 num_things: " << acc1.num_things << std::endl;
    std::cout << "acc2 num_things: " << acc2.num_things << std::endl;

    return 0;
}

std::ref 是 C++ 标准库中的一个函数模板,用于将一个对象包装成一个引用,从而能够在函数调用中传递引用语义而不是传值语义。,std::ref(acc1)acc1 这个对象包装成一个引用,使得在 std::thread 的构造函数中能够传递引用而不是拷贝对象。如果不使用 std::ref,则 std::thread 的构造函数默认会拷贝传递参数。

等价于

std::thread t1(transfer, &acc1, &acc2, 10);
std::thread t2(transfer, &acc2, &acc1, 5);

相关推荐

  1. 2024.4.9记——C++线系列文章(五)

    2023-12-25 10:08:02       12 阅读
  2. 以及如何避免

    2023-12-25 10:08:02       16 阅读
  3. C++线编程】(一)详解互斥mutex

    2023-12-25 10:08:02       41 阅读
  4. C# 线编程线与无并发

    2023-12-25 10:08:02       17 阅读
  5. 线(42)无编程

    2023-12-25 10:08:02       16 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-25 10:08:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-25 10:08:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-25 10:08:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-25 10:08:02       20 阅读

热门阅读

  1. 【大数据学习笔记】新手学习路线图

    2023-12-25 10:08:02       39 阅读
  2. HTTP 简介 (js)

    2023-12-25 10:08:02       36 阅读
  3. 【C#与Redis】--高级主题--Redis 事务

    2023-12-25 10:08:02       26 阅读
  4. 13.bash shell中的if-then语句

    2023-12-25 10:08:02       38 阅读
  5. 从命令行里打开pycharm项目

    2023-12-25 10:08:02       34 阅读
  6. @RequestMapping详解:请求映射规则

    2023-12-25 10:08:02       45 阅读
  7. Flash、Ajax各自的优缺点,在使用中如何取舍

    2023-12-25 10:08:02       30 阅读
  8. Linux: dev: cmake: CHECK_LIBRARY_EXISTS

    2023-12-25 10:08:02       35 阅读