C++ 多线程实现死锁

什么是死锁——多个线程循环等待对方释放所需的资源

一.死锁的发生

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";
}

https://blog.csdn.net/feixiaoxing/article/details/7036264#

https://blog.csdn.net/shaochuang1/article/details/100998094

猜你喜欢

转载自blog.csdn.net/weixin_43202635/article/details/115399097