51 C++ 死锁问题

什么是死锁?

线程A 需要拿到两个mutex才能执行完毕,一个mutex1,一个mutex2,注意是有顺序的

线程B 需要拿到两个mutex才能执行完毕,一个mutex2,一个mutex1,注意是有顺序的

当线程A 拿到mutex1后,马上去拿mutex2,这时候由于线程调度。

系统调用线程B,这时候线程B 是可以拿到mutex2,但是拿不到mutex1,因为mutex1是被线程A拿着的,

于是就变成了A 拿着mutex1,然后等mutex2才能往下走。

B 拿着mutex2,然后等mutex1才能往下走。

都卡着,于是死锁发生了

死锁发生的条件

1.需要至少两个mutex。

2.需要至少两个线程。

3.两个线程需要同样的多个mutex

---这句话的意思是: A 线程 需要 mutex1,mutex2

B线程也需要mutex1,和mutex2

4.线程A 和线程B 对于mutex1,和 mutex2 的需求顺序不同。

-----这句话的意思是:A线程需要 mutex1保护一段数据,然后需要mutex2保护一段数据。

而B线程是先需要mutex2保护一段数据,然后再需要mutex1保护一段数据

死锁演示

//死锁演示

class Teacher167 {

public:
	//共享数据 存在list中
	list<int> msgListQueue;
	mutex mymutex1;
	mutex mymutex2;
	int readcount = 0;//记录已经读取了多少个。
public:
	//读取 共享数据的线程方法
	void readfunc() {
		while (true) {
			//只要不为空,就可以读取数据
			if (readcount >= 20000) {
				break;
			}
			{
				//先使用mymutex1 处理一段数据
				mymutex1.lock();//mylock_guard在这里创建出来,在mylock_guard的构造函数中,就已经对mymutex进行了lock()操作,等到mylock_guard的生命周期结束后会析构,这时候会调用mymutex的unlock()函数
				mymutex2.lock();
				if (!msgListQueue.empty()) {
					int readvalue = msgListQueue.front();//每次都读取第一个
					cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl;
					msgListQueue.pop_front();//删除第一个元素
					readcount++;
					mymutex1.unlock();
					mymutex2.unlock();
				}
				else {
					cout << "没有读取到值" << endl;
					mymutex1.unlock();
					mymutex2.unlock();
				}

			}
		}
	}

	//写入 共享数据的线程方法
	void writefunc() {
		for (size_t i = 0; i < 10000; i++)
		{
			mymutex2.lock();
			mymutex1.lock();
			msgListQueue.push_back(i);//每次都写到末尾
			cout << "写入元素的值为" << i << endl;
			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

public:
	Teacher167() {
		cout << "Teacher167 构造方法 this = " << this << endl;
	}

	Teacher167(const Teacher167 & obj) {
		cout << "Teacher167 copy 构造方法 this = " << this << "  obj = " << &obj << endl;
	}

	~Teacher167() {
		cout << "Teacher167 析构函数 this = " << this << endl;
	}
};


void main() {

	cout << "=========================" << endl;

	Teacher167 tea1;
	thread readthread1(&Teacher167::readfunc, &tea1);
	thread writethread1(&Teacher167::writefunc, &tea1);

	readthread1.join();
	writethread1.join();
}

死锁演示2

访问不同的数据也是一样的

//死锁演示2

class Teacher168 {

public:
	//共享数据 存在list中
	list<int> msgListQueue;
	list<int> msgListQueue2;
	mutex mymutex1;
	mutex mymutex2;
	int readcount = 0;//记录已经读取了多少个。
public:
	//读取 共享数据的线程方法
	void readfunc() {
		while (true) {
			//只要不为空,就可以读取数据
			if (readcount >= 20000) {
				break;
			}
			
			//先使用mymutex1 处理一段数据
			mymutex1.lock();//mylock_guard在这里创建出来,在mylock_guard的构造函数中,就已经对mymutex进行了lock()操作,等到mylock_guard的生命周期结束后会析构,这时候会调用mymutex的unlock()函数
			if (!msgListQueue.empty()) {
					int readvalue = msgListQueue.front();//每次都读取第一个
					cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl;
					msgListQueue.pop_front();//删除第一个元素
					readcount++;
			}
			else {
					cout << "没有读取到值" << endl;
			}

			mymutex2.lock();
			if (!msgListQueue2.empty()) {
				int readvalue = msgListQueue2.front();//每次都读取第一个
				cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl;
				msgListQueue2.pop_front();//删除第一个元素
				readcount++;
			}
			else {
				cout << "222没有读取到值" << endl;
			}

			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

	//写入 共享数据的线程方法
	void writefunc() {
		for (size_t i = 0; i < 10000; i++)
		{
			mymutex2.lock();
			mymutex1.lock();
			msgListQueue.push_back(i);//每次都写到末尾
			cout << "写入元素的值为" << i << endl;
			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

public:
	Teacher168() {
		cout << "Teacher168 构造方法 this = " << this << endl;
	}

	Teacher168(const Teacher168 & obj) {
		cout << "Teacher168 copy 构造方法 this = " << this << "  obj = " << &obj << endl;
	}

	~Teacher168() {
		cout << "Teacher168 析构函数 this = " << this << endl;
	}
};


void main() {

	cout << "=========================" << endl;

	Teacher168 tea1;
	thread readthread1(&Teacher168::readfunc, &tea1);
	thread writethread1(&Teacher168::writefunc, &tea1);
	thread readthread2(&Teacher168::readfunc, &tea1);
	thread writethread2(&Teacher168::writefunc, &tea1);
	readthread1.join();
	writethread1.join();
	readthread1.join();
	writethread1.join();
}

fix 死锁问题

方案一:只要保证着两个互斥量的顺序一致,就不会死锁。使用lock_guard也是一样的。

class Teacher168 {

public:
	//共享数据 存在list中
	list<int> msgListQueue;
	list<int> msgListQueue2;
	mutex mymutex1;
	mutex mymutex2;
	int readcount = 0;//记录已经读取了多少个。
public:
	//读取 共享数据的线程方法
	void readfunc() {
		while (true) {
			//只要不为空,就可以读取数据
			if (readcount >= 20000) {
				break;
			}
			
			//先使用mymutex1 处理一段数据
			mymutex1.lock();//mylock_guard在这里创建出来,在mylock_guard的构造函数中,就已经对mymutex进行了lock()操作,等到mylock_guard的生命周期结束后会析构,这时候会调用mymutex的unlock()函数
			if (!msgListQueue.empty()) {
					int readvalue = msgListQueue.front();//每次都读取第一个
					cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl;
					msgListQueue.pop_front();//删除第一个元素
					readcount++;
			}
			else {
					cout << "没有读取到值" << endl;
			}

			mymutex2.lock();
			if (!msgListQueue2.empty()) {
				int readvalue = msgListQueue2.front();//每次都读取第一个
				cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl;
				msgListQueue2.pop_front();//删除第一个元素
				readcount++;
			}
			else {
				cout << "222没有读取到值" << endl;
			}

			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

	//写入 共享数据的线程方法
	void writefunc() {
		for (size_t i = 0; i < 10000; i++)
		{
			//fix方案,让 mymutex1.lock(),和 mymutex2.lock() 的顺序和前面的一样。
			mymutex1.lock();
			mymutex2.lock();

			msgListQueue.push_back(i);//每次都写到末尾
			cout << "写入元素的值为" << i << endl;
			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

public:
	Teacher168() {
		cout << "Teacher168 构造方法 this = " << this << endl;
	}

	Teacher168(const Teacher168 & obj) {
		cout << "Teacher168 copy 构造方法 this = " << this << "  obj = " << &obj << endl;
	}

	~Teacher168() {
		cout << "Teacher168 析构函数 this = " << this << endl;
	}
};


void main() {

	cout << "=========================" << endl;

	Teacher168 tea1;
	thread readthread1(&Teacher168::readfunc, &tea1);
	thread writethread1(&Teacher168::writefunc, &tea1);
	thread readthread2(&Teacher168::readfunc, &tea1);
	thread writethread2(&Teacher168::writefunc, &tea1);
	readthread1.join();
	writethread1.join();
	readthread1.join();
	writethread1.join();
}

方案二:std::lock(mutex1,mutex2)函数

使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了

lock(mymutex1, mymutex2);

class Teacher169 {

public:
	//共享数据 存在list中
	list<int> msgListQueue;
	list<int> msgListQueue2;
	mutex mymutex1;
	mutex mymutex2;
	int readcount = 0;//记录已经读取了多少个。
public:
	//读取 共享数据的线程方法
	void readfunc() {
		while (true) {
			//只要不为空,就可以读取数据
			if (readcount >= 20000) {
				break;
			}

			//fix方案2.使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了
			lock(mymutex1, mymutex2);

			if (!msgListQueue.empty()) {
				int readvalue = msgListQueue.front();//每次都读取第一个
				cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl;
				msgListQueue.pop_front();//删除第一个元素
				readcount++;
			}
			else {
				cout << "没有读取到值" << endl;
			}

			
			if (!msgListQueue2.empty()) {
				int readvalue = msgListQueue2.front();//每次都读取第一个
				cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl;
				msgListQueue2.pop_front();//删除第一个元素
				readcount++;
			}
			else {
				cout << "222没有读取到值" << endl;
			}

			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

	//写入 共享数据的线程方法
	void writefunc() {
		for (size_t i = 0; i < 10000; i++)
		{
			//fix方案2,使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了
			lock(mymutex1, mymutex2);
			msgListQueue.push_back(i);//每次都写到末尾
			cout << "写入元素的值为" << i << endl;
			mymutex1.unlock();
			mymutex2.unlock();
		}
	}

public:
	Teacher169() {
		cout << "Teacher169 构造方法 this = " << this << endl;
	}

	Teacher169(const Teacher169 & obj) {
		cout << "Teacher169 copy 构造方法 this = " << this << "  obj = " << &obj << endl;
	}

	~Teacher169() {
		cout << "Teacher169 析构函数 this = " << this << endl;
	}
};


void main() {

	cout << "=========================" << endl;

	Teacher169 tea1;
	thread readthread1(&Teacher169::readfunc, &tea1);
	thread writethread1(&Teacher169::writefunc, &tea1);
	thread readthread2(&Teacher169::readfunc, &tea1);
	thread writethread2(&Teacher169::writefunc, &tea1);
	readthread1.join();
	writethread1.join();
	readthread1.join();
	writethread1.join();
}

方案二进阶版:使用 lock_guard<mutex> 替代 unlock

//fix方案进阶版.使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了

// 这里使用 lock_guard<mutex> sbguard1(mymutex1,adopt_lock);的含义是:在lock_guard构造函数中不调用 mutex1.lock(),只是在析构函数中调用 mutex1.unlock();

lock(mymutex1, mymutex2);

lock_guard<mutex> sbguard1(mymutex1,adopt_lock);

lock_guard<mutex> sbguard2(mymutex2, adopt_lock);

class Teacher170 {

public:
	//共享数据 存在list中
	list<int> msgListQueue;
	list<int> msgListQueue2;
	mutex mymutex1;
	mutex mymutex2;
	int readcount = 0;//记录已经读取了多少个。
public:
	//读取 共享数据的线程方法
	void readfunc() {
		while (true) {
			//只要不为空,就可以读取数据
			if (readcount >= 20000) {
				break;
			}

			//fix方案3.使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了
			// 这里使用 lock_guard<mutex> sbguard1(mymutex1,adopt_lock);的含义是:在lock_guard构造函数中不调用 mutex1.lock(),只是在析构函数中调用 mutex1.unlock();
			lock(mymutex1, mymutex2);
			lock_guard<mutex> sbguard1(mymutex1,adopt_lock);
			lock_guard<mutex> sbguard2(mymutex2, adopt_lock);

			if (!msgListQueue.empty()) {
				int readvalue = msgListQueue.front();//每次都读取第一个
				cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl;
				msgListQueue.pop_front();//删除第一个元素
				readcount++;
			}
			else {
				cout << "没有读取到值" << endl;
			}


			if (!msgListQueue2.empty()) {
				int readvalue = msgListQueue2.front();//每次都读取第一个
				cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl;
				msgListQueue2.pop_front();//删除第一个元素
				readcount++;
			}
			else {
				cout << "222没有读取到值" << endl;
			}
		}
	}

	//写入 共享数据的线程方法
	void writefunc() {
		for (size_t i = 0; i < 10000; i++)
		{
			//fix方案2,使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了
			lock(mymutex1, mymutex2);
			lock_guard<mutex> sbguard1(mymutex1, adopt_lock);
			lock_guard<mutex> sbguard2(mymutex2, adopt_lock);
			msgListQueue.push_back(i);//每次都写到末尾
			cout << "写入元素的值为" << i << endl;
		}
	}

public:
	Teacher170() {
		cout << "Teacher170 构造方法 this = " << this << endl;
	}

	Teacher170(const Teacher170 & obj) {
		cout << "Teacher170 copy 构造方法 this = " << this << "  obj = " << &obj << endl;
	}

	~Teacher170() {
		cout << "Teacher170 析构函数 this = " << this << endl;
	}
};


void main() {

	cout << "=========================" << endl;

	Teacher170 tea1;
	thread readthread1(&Teacher170::readfunc, &tea1);
	thread writethread1(&Teacher170::writefunc, &tea1);
	thread readthread2(&Teacher170::readfunc, &tea1);
	thread writethread2(&Teacher170::writefunc, &tea1);
	readthread1.join();
	writethread1.join();
	readthread2.join();
	writethread2.join();
}
相关推荐
别NULL2 小时前
机试题——疯长的草
数据结构·c++·算法
CYBEREXP20083 小时前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
yuanbenshidiaos4 小时前
c++------------------函数
开发语言·c++
yuanbenshidiaos4 小时前
C++----------函数的调用机制
java·c++·算法
tianmu_sama4 小时前
[Effective C++]条款38-39 复合和private继承
开发语言·c++
羚羊角uou4 小时前
【C++】优先级队列以及仿函数
开发语言·c++
姚先生975 小时前
LeetCode 54. 螺旋矩阵 (C++实现)
c++·leetcode·矩阵
FeboReigns5 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns5 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++
264玫瑰资源库5 小时前
从零开始C++棋牌游戏开发之第二篇:初识 C++ 游戏开发的基本架构
开发语言·c++·架构