多线程:死锁
目录
死锁的条件
死锁的示例
死锁的预防与解决
死锁的检测
总结
死锁(Deadlock)是多线程或多进程环境中一种特定的状态,指的是两个或多个线程或进程在执行过程中,由于争夺资源而造成的一种相互等待的状态,导致它们无法继续执行下去。当出现死锁时,相关的线程或进程都处于阻塞状态,无法获得所需的资源。
有这么一个段子:这天你去面试,HR问你什么是死锁。这时你和HR说:你给我发offer,我就告诉你什么是死锁。那HR又说:你告诉我什么是死锁,我就给你发offer。于是就这么周而复始,HR和你都在等待对方松口,于是就形成了一个循环,这就是死锁状态。
段子中的死锁分析:
-
互斥条件:你和HR各自持有对方所需的东西(你的知识和HR的offer),双方都不能同时拥有。
-
保持并等待条件:你在等待HR发offer,而HR则在等待你讲解什么是死锁。两者都在持有自己所拥有的“资源”同时等待对方。
-
不剥夺条件:你已经持有的(对死锁的了解)无法被强制夺取,而HR的offer也无法被你强行获取。
-
循环等待条件:你和HR形成了一个循环:你等待HR,HR等待你,所以形成了一个死锁状态。
死锁的条件
为了了解死锁的本质,需要认识到死锁发生的必要条件,这通常被称为死锁的四个必要条件:
-
互斥条件:至少有一个资源必须被一个线程占用,而此时此资源不能被其他线程占用。
-
保持并等待条件:一个线程至少持有一个资源,同时又在等待获取其他线程持有的资源。
-
不剥夺条件:已经分配给线程的资源,在未使用完之前,不能被其他线程强行夺取。
-
循环等待条件:存在一个线程的集合,线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,线程 C 又等待线程 A 持有的资源。这构成了一个环路。
死锁的示例
考虑以下示例,说明死锁的发生:
#include <iostream>
#include <thread>
#include <mutex>// 定义两个互斥锁
std::mutex mutexA;
std::mutex mutexB;// 线程1
void thread1Func() {std::lock_guard<std::mutex> lockA(mutexA);std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些工作std::lock_guard<std::mutex> lockB(mutexB); // 可能会死锁std::cout << "Thread 1 acquired both locks" << std::endl;
}// 线程2
void thread2Func() {std::lock_guard<std::mutex> lockB(mutexB);std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些工作std::lock_guard<std::mutex> lockA(mutexA); // 可能会死锁std::cout << "Thread 2 acquired both locks" << std::endl;
}int main() {std::thread thread1(thread1Func);std::thread thread2(thread2Func);thread1.join();thread2.join();return 0;
}
在这个代码示例中,thread1Func
和 thread2Func
两个线程可能会在获取锁的过程中造成死锁。假设:
- 线程1 先获取了
mutexA
,然后尝试去获取mutexB
。 - 线程2 先获取了
mutexB
,然后尝试去获取mutexA
。
如果两个线程在此时同时获取了各自的锁,就会造成互相等待,形成死锁。
死锁的预防与解决
为了避免死锁,可以采取以下几种策略:
-
资源有序分配:通过按顺序请求资源,确保不会形成环路。例如,总是先请求
mutexA
再请求mutexB
。 -
资源抢占:如果一个线程被阻塞,它所持有的资源可以被强制释放,允许其他线程获取资源。
-
超时机制:给资源请求设置一个超时时间,超过时间的话,放弃所占用的资源,稍后再尝试获取。
-
死锁检测和恢复:运行时监控系统状态,以发现和处理死锁。如果检测到死锁,可以通过终止某些线程或强制释放资源来恢复系统。
死锁的检测
在一些高级系统中,可以使用算法来检测死锁,如银行家算法、等待图和资源分配图等,以便找出死锁发生的根本原因并采取措施解决它。
总结
死锁是多线程编程中一个需要谨慎处理的问题,了解其发生的条件和预防策略能够帮助我们编写更为健壮和高效的代码。通过合理的资源管理和调度,可以有效减小甚至避免死锁的发生。