MST

星途 面试题库

面试题:C++多线程互斥场景下的死锁分析与解决

在C++多线程编程中,存在两个线程T1和T2,T1首先锁定互斥锁M1,然后尝试锁定互斥锁M2;T2首先锁定互斥锁M2,然后尝试锁定互斥锁M1。请分析这种场景下可能产生死锁的原因,并提出至少两种避免死锁的方法,用代码示例说明其中一种方法。
22.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

死锁原因

当T1锁定M1后,T2锁定M2,此时T1尝试锁定M2,由于M2已被T2锁定,T1进入等待;同时T2尝试锁定M1,由于M1已被T1锁定,T2也进入等待。两个线程互相等待对方释放锁,从而形成死锁。

避免死锁的方法

  1. 破坏死锁的四个必要条件(互斥、占有并等待、不可剥夺、循环等待)
    • 破坏占有并等待:线程在开始时一次性获取所有需要的锁,而不是分阶段获取。
    • 破坏不可剥夺:当一个线程获取锁失败时,释放已持有的锁。
    • 破坏循环等待:对锁进行排序,线程按照相同顺序获取锁。
  2. 使用资源分配图算法(如银行家算法):在每次请求资源(锁)时,检查是否会导致死锁。

代码示例(按照锁顺序获取锁的方法)

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

std::mutex M1;
std::mutex M2;

void thread1() {
    // 按照固定顺序获取锁
    std::lock(M1, M2);
    std::lock_guard<std::mutex> lock1(M1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(M2, std::adopt_lock);

    std::cout << "Thread 1 is working." << std::endl;
}

void thread2() {
    // 按照固定顺序获取锁
    std::lock(M1, M2);
    std::lock_guard<std::mutex> lock1(M1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(M2, std::adopt_lock);

    std::cout << "Thread 2 is working." << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);

    t1.join();
    t2.join();

    return 0;
}

在这个示例中,std::lock 函数会一次性获取两个锁,避免了死锁的发生。std::lock_guard 使用 std::adopt_lock 标记,表示已经获取了锁,不需要在构造时再次获取。这样两个线程都会按照相同的顺序获取锁,从而避免死锁。