【C++并发编程】(六)死锁问题

本文介绍了死锁的概念,包括互斥、占有等待、不可抢占和循环等待四个必要条件,以及在C++中如何通过std::lock和std::unique_lock的配合避免死锁,强调了锁顺序策略的重要性。
摘要由CSDN通过智能技术生成

(六)解决死锁问题

死锁(Deadlock)是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(或线程)称为死锁进程(或线程)。

死锁的发生通常需要同时满足四个必要条件:

  1. 互斥条件:某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程(或线程)占有。
  2. 占有且等待条件:进程(或线程)至少已经占有一个资源,但又申请新的资源。
  3. 不可抢占条件:一个进程(或线程)所占有的资源在其使用完之前,不能被其他进程(或线程)强行夺走,只能由该进程(或线程)用完之后主动释放。
  4. 循环等待条件:存在一个进程(或线程)等待序列{P1, P2, …, Pn},其中P1等待P2所占有的某个资源,P2等待P3所占有的某个资源,…,而Pn等待P1所占有的某个资源,从而形成一个进程(或线程)循环等待的局面。

例如,一条生产线上,每一个工具同一时刻只能被一人使用(互斥条件),每个人手里已经有一个工具了,还想要下一个人手里的工具(占有且等待),每个人的工具都不会被被动地剥夺(不可抢占条件),每个人都在等着下一个人手里的工具,而最后一个人又在等待第一个人手里的工具(循环等待条件)。

下面是一个有死锁问题的代码示例:

#include <iostream>  
#include <thread>  
#include <mutex>  
  
std::mutex mtxA;  
std::mutex mtxB;  
  
void threadFunction1() {  
    // 线程1先锁定互斥锁A  
    std::lock_guard<std::mutex> lockA(mtxA);  
    std::cout << "Thread 1: Locked mtxA, trying to lock mtxB\n";  
  
    // 试图锁定互斥锁B,若此时如果线程2已经锁定了mtxB,则线程1会阻塞  
    std::lock_guard<std::mutex> lockB(mtxB); 
    std::cout << "Thread 1: Locked mtxB\n";  

}  
  
void threadFunction2() {  
    // 线程2先锁定互斥锁B  
    std::lock_guard<std::mutex> lockB(mtxB);  
    std::cout << "Thread 2: Locked mtxB, trying to lock mtxA\n";  
  
    // 试图锁定互斥锁A,但此时如果线程1已经锁定了mtxA,则线程2会阻塞  
    std::lock_guard<std::mutex> lockA(mtxA); 
    std::cout << "Thread 2: Locked mtxA\n";  

}  
  
int main() {  
    // 创建两个线程  
    std::thread t1(threadFunction1);  
    std::thread t2(threadFunction2);  
  
    // 等待两个线程完成  
    t1.join();  
    t2.join();  
  
    return 0;  
}
Thread 1: Locked mtxA, trying to lock mtxB
Thread 2: Locked mtxB, trying to lock mtxA

在这个示例中:

  • 互斥条件:由std::mutex类保证,每个互斥锁在同一时间只能被一个线程锁定。
  • 占有且等待条件:线程1锁定了mtxA并等待mtxB,而线程2锁定了mtxB并等待mtxA
  • 不可抢占条件:一旦线程锁定了互斥锁,没有其他线程可以抢占它,直到原始线程调用解锁。
  • 循环等待条件:线程1等待线程2释放mtxB,而线程2等待线程1释放mtxA,形成了一个循环等待。

由于这四个条件都满足,因此这个程序可能会导致死锁。

为了解决上述代码中的死锁问题,我们可以使用一种称为“锁顺序”或“锁排序”的策略,即确保所有线程都以相同的顺序请求锁。这样可以避免循环等待的情况,从而防止死锁:

#include <iostream>
#include <thread>
#include <mutex>
  
std::mutex mtxA;  
std::mutex mtxB;  
  
void threadFunction1() { 
    std::unique_lock<std::mutex> lockA(mtxA, std::defer_lock); // 延迟锁定 mtxA
    std::unique_lock<std::mutex> lockB(mtxB, std::defer_lock); // 延迟锁定 mtxB

    // 使用 std::lock 确保两个锁以正确的顺序被锁定
    std::lock(lockA, lockB);
    // mtxA 和 mtxB 都被锁定了,并且是按照正确的顺序
    std::cout << "Thread 1: Both mtxA and mtxB are locked.\n";

}  
  
void threadFunction2() {  
    std::unique_lock<std::mutex> lockB(mtxB, std::defer_lock); // 延迟锁定 mtxB
    std::unique_lock<std::mutex> lockA(mtxA, std::defer_lock); // 延迟锁定 mtxA
    
    // 使用 std::lock 确保两个锁以正确的顺序被锁定
    std::lock(lockA, lockB);
    // mtxA 和 mtxB 都被锁定了,并且是按照正确的顺序
    std::cout << "Thread 2: Both mtxA and mtxB are locked.\n";

}  
  
int main() {  
    // 创建两个线程  
    std::thread t1(threadFunction1);  
    std::thread t2(threadFunction2);  
  
    // 等待两个线程完成  
    t1.join();  
    t2.join();  
  
    return 0;  
}
Thread 1: Both mtxA and mtxB are locked.
Thread 2: Both mtxA and mtxB are locked.

在上面的代码中使用 std::unique_lockstd::lock 来确保 mtxAmtxB 以正确的顺序被锁定。std::lock 函数会尝试以原子方式锁定多个互斥锁,如果无法立即获得所有锁,它会阻塞直到所有锁都变得可用,或者抛出异常(取决于 std::lock 的配置)。

注意,std::unique_lock 的构造函数中使用了 std::defer_lock 参数来延迟锁定(std::lock_guard 不支持延迟锁定),这样我们才可以在调用 std::lock 时以原子方式锁定多个互斥锁。如果直接使用 std::lock_guard 而不延迟锁定,那么每个 std::lock_guard 会立即尝试锁定其对应的互斥锁,这样就无法使用 std::lock 来协调多个锁的锁定顺序了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二进制人工智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值