面试题答案
一键面试死锁原因
当T1锁定M1后,T2锁定M2,此时T1尝试锁定M2,由于M2已被T2锁定,T1进入等待;同时T2尝试锁定M1,由于M1已被T1锁定,T2也进入等待。两个线程互相等待对方释放锁,从而形成死锁。
避免死锁的方法
- 破坏死锁的四个必要条件(互斥、占有并等待、不可剥夺、循环等待):
- 破坏占有并等待:线程在开始时一次性获取所有需要的锁,而不是分阶段获取。
- 破坏不可剥夺:当一个线程获取锁失败时,释放已持有的锁。
- 破坏循环等待:对锁进行排序,线程按照相同顺序获取锁。
- 使用资源分配图算法(如银行家算法):在每次请求资源(锁)时,检查是否会导致死锁。
代码示例(按照锁顺序获取锁的方法)
#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
标记,表示已经获取了锁,不需要在构造时再次获取。这样两个线程都会按照相同的顺序获取锁,从而避免死锁。