什么是死锁——多个线程循环等待对方释放所需的资源
一.死锁的发生
1. 忘记释放锁
void data_process()
{
EnterCriticalSection();
if(/* error happens */)
return;
LeaveCriticalSection();
}
2. 单线程重复申请锁
void sub_func()
{
EnterCriticalSection();
do_something();
LeaveCriticalSection();
}
void data_process()
{
EnterCriticalSection();
sub_func(); //sub_func()重复申请锁
LeaveCriticalSection();
}
3. 双线程多锁申请
void data_process1()
{
EnterCriticalSection(&cs1); //申请锁1
EnterCriticalSection(&cs2); //申请锁2
do_something1();
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
}
void data_process2()
{
EnterCriticalSection(&cs2); //申请锁2
EnterCriticalSection(&cs1); //申请锁1
do_something2();
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs2);
}
4. 环形锁申请
/*
* A - B
* | |
* C - D
*/
死锁代码示例:
#include <iostream>
#include <thread>
#include <mutex>
#include <deque>
class A{
private:
std::deque<int> my_deque;
std::mutex my_mutex_1;
std::mutex my_mutex_2;
public:
void WriteFunction(){
for (int i = 0; i < 1000; ++i){
my_mutex_1.lock();
my_mutex_2.lock();
std::cout << "向队列中添加一个元素" << std::endl;
my_deque.push_back(i);
my_mutex_1.unlock();
my_mutex_2.unlock();
}
}
void ReadFunction(){
for (int i = 0; i < 1000; ++i){
my_mutex_2.lock();
my_mutex_1.lock();
if (!my_deque.empty()){
std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
my_deque.pop_front();
}
my_mutex_2.unlock();
my_mutex_1.unlock();
}
}
};
int main(){
A a;
std::thread my_thread_1(&A::WriteFunction, std::ref(a));
std::thread my_thread_2(&A::ReadFunction, std::ref(a));
my_thread_1.join();
my_thread_2.join();
std::cout << "Hello World!\n";
}
1、可会合(joinable):这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。
2、相分离(detached):表示子线程无需和主线程会合,也就是相分离的,这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。
在任何一个时间点上,线程是可结合(joinable)或者是可分离的(detached),一个可结合的线程能够被其他线程回收资源和杀死,在被其他线程回收之前,它的存储器资源如栈,是不释放的,相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
解决办法:
对比前面的代码,就会发现我们用std::lock(my_mutex_1, my_mutex_2);
替代了,my_mutex_1.lock();my_mutex_2.lock();
这两句。
std::lock(my_mutex_1, my_mutex_2);
的作用就是,同时去锁my_mutex_1, my_mutex_2
,必须同时锁成功,一旦有一个互斥量不能被锁,线程就会卡在这里,直至两个锁可以被同时锁成功,这样就避免了死锁的现象。
#include <iostream>
#include <thread>
#include <mutex>
#include <deque>
class A{
private:
std::deque<int> my_deque;
std::mutex my_mutex_1;
std::mutex my_mutex_2;
public:
void WriteFunction(){
for (int i = 0; i < 1000; ++i){
std::lock(my_mutex_1, my_mutex_2);
std::cout << "向队列中添加一个元素" << std::endl;
my_deque.push_back(i);
my_mutex_1.unlock();
my_mutex_2.unlock();
}
}
void ReadFunction(){
for (int i = 0; i < 1000; ++i){
std::lock(my_mutex_1, my_mutex_2);
if (!my_deque.empty()){
std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
my_deque.pop_front();
}
my_mutex_2.unlock();
my_mutex_1.unlock();
}
}
};
int main()
{
A a;
std::thread my_thread_1(&A::WriteFunction, std::ref(a));
std::thread my_thread_2(&A::ReadFunction, std::ref(a));
my_thread_1.join();
my_thread_2.join();
std::cout << "Hello World!\n";
}