c++11多线程互斥量的概念用法死锁演示及解决详解

u012507022 u012507022     2022-12-11     422

关键词:

目录

1.互斥量(mutex)的基本概念

2.互斥量的用法

2.1 lock()、unlock()

2.2 std::lock_guard类模板

3.死锁

3.1 死锁演示

3.2 死锁的一般解决方案

3.3 std::lock()函数模板

3.4 std::lock_guard()的std::adopt_lock参数


1.互斥量(mutex)的基本概念

保护共享数据,操作时,某个线程用代码把共享数据锁住、然后操作数据、最后解锁;其它想操作共享数据的线程必须等待解锁;

互斥量是个类对象,理解成一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁成功(成功的标志是lock()函数返回);如果没锁成功,那么这个流程卡在lock(),不断的尝试去锁这把锁头;

互斥量使用要小心,保护数据不多也不能少,少了,没有达到保护的效果,多了,影响效率;

2.互斥量的用法

2.1 lock()、unlock()

步骤:先lock(),操作共享数据,unlock()

lock()与unlock()要成对使用,有lock()必然要有unlock(),每调用一次lock(),必然应该调用一次unlock();不应该也不允许调用一次lock(),却调用了2次unlock(),这些非对称数量的调用都会导致代码不稳定甚至崩溃。实例代码如下:

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>

using namespace std;

//准备用成员函数作为线程函数的方法写线程
class A

public:
	//把收到的消息入到一个队列的线程
	void inMsgRecvQueue() 
	
		for (int i = 0; i < 10000;i++)
		
			cout << "inMsgRecvQueue()执行,插入一个元素"<< i<<endl;
			my_mutex.lock();
			msgRecvQueue.push_back(i); //假设这个数字i就是收到的命令,直接弄到消息队列里边来;
			my_mutex.unlock();
		
	

	bool outMsgLULProc(int &command)
	
		my_mutex.lock();
		if (!msgRecvQueue.empty())
		
			//消息不为空
			int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();//移除第一个元素。但不返回;
			my_mutex.unlock();  //所有分支都必须有unlock()
			return true;
		
		my_mutex.unlock();
		return false;
	
	//把数据从消息队列取出的线程
	void outMsgRecvQueue()
	
		int command = 0;
		for (int i = 0; i < 10000; i++)
		
			bool result = outMsgLULProc(command);

			if (result  == true)
			
				cout << "outMsgRecvQueue()执行,取出一个元素"<< endl;
				//处理数据
			
			else
			
				//消息队列为空
				cout << "inMsgRecvQueue()执行,但目前消息队列中为空!" << i << endl;
			
		
		cout <<"end!" << endl;
	

private:
	std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。
	std::mutex my_mutex;
;

int main()

	A myobja;

	std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);//第二个参数,引用,才能保证线程里用的是同一个对象
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myOutMsgObj.join();
	myInMsgObj.join();

	cout << "主线程执行!" << endl;

	return 0;

注:有lock(),忘记unlock()的问题很难排查;

为了防止大家忘记unlock(),引入了一个叫std::lock_guard的类模板:忘记unlock(),替你unlock()。如同智能指针(unique_ptr<>),你忘记释放内存不要紧,我替你释放。

2.2 std::lock_guard类模板

直接取代lock()与unlock();也就是说,你用了lock_guard之后,再不能使用lock()和unlock()。std::lock_guard类模板的原理很简单,lock_guard构造函数里执行了mutex::lock();ock_guard析构函数里执行了mutex::unlock()。实例代码如下:

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>

using namespace std;

//准备用成员函数作为线程函数的方法写线程
class A

public:
	//把收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	
		for (int i = 0; i < 10000; i++)
		
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			  //大括号提前结束lock_guard生命周期
				std::lock_guard<std::mutex> sbguard(my_mutex); 
				//my_mutex.lock();
				msgRecvQueue.push_back(i); //假设这个数字i就是收到的命令,直接弄到消息队列里边来;
				//my_mutex.unlock();
			
		
	

	bool outMsgLULProc(int &command)
	
		std::lock_guard<std::mutex> sbguard(my_mutex);//sbguard时对象名
		//lock_guard构造函数里执行了mutex::lock()
		//lock_guard析构函数里执行了mutex::unlock()
		//my_mutex.lock();
		if (!msgRecvQueue.empty())
		
			//消息不为空
			int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();//移除第一个元素。但不返回;
			//my_mutex.unlock();  //所有分支都必须有unlock()
			return true;
		
		//my_mutex.unlock();
		return false;
	
	//把数据从消息队列取出的线程
	void outMsgRecvQueue()
	
		int command = 0;
		for (int i = 0; i < 10000; i++)
		
			bool result = outMsgLULProc(command);

			if (result == true)
			
				cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
				//处理数据
			
			else
			
				//消息队列为空
				cout << "inMsgRecvQueue()执行,但目前消息队列中为空!" << i << endl;
			
		
		cout << "end!" << endl;
	

private:
	std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。
	std::mutex my_mutex;//创建一个互斥量(一把锁)
;

int main()

	A myobja;

	std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);//第二个参数,引用,才能保证线程里用的是同一个对象
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myOutMsgObj.join();
	myInMsgObj.join();

	cout << "主线程执行!" << endl;

	return 0;

3.死锁

通俗解释:

张三:站在北京,等李四,不挪窝;

李四:站在深圳,等张三,不挪窝;

C++ 中:

比如有两把锁(死锁问题,是由至少两把锁头[两个互斥量才能] 产生);

问题分析:

两个线程A、B;两把锁:金锁(jinlock),银锁(yinlock)

(1)线程A执行的时候,这个线程先锁,把金锁lock()成功了,然后它去lock银锁。。。

(2)此时出现了上下文切换

(3)线程B执行了,这个线程先锁银锁,因为银锁还没被锁,所以银锁会lock()成功,线程B要去lock()金锁。。。

(4)此时此刻,死锁就产生了;

(5)线程A因为拿不到银锁头,流程走不下去(所有后边代码有解金锁的但是流程走不下去,所以金锁解不开)

(6)线程B因为拿不到金锁头,流程走不下去(所有后边代码有解银锁的但是流程走不下去,所以银锁解不开)

大家都晾在那里,你等我,我等你

3.1 死锁演示

两个线程上锁的顺序正好是反着的。 实例代码如下:

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>

using namespace std;

//准备用成员函数作为线程函数的方法写线程
class A

public:
	//把收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	
		for (int i = 0; i < 10000; i++)
		
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

			my_mutex1.lock();//实际工程中,这两个锁并不定挨着,可能他们需要保护不同的数据共享块
			my_mutex2.lock();
			msgRecvQueue.push_back(i); //假设这个数字i就是收到的命令,直接弄到消息队列里边来;
			my_mutex2.unlock();
			my_mutex1.unlock();
		
	

	bool outMsgLULProc(int &command)
	
		//std::lock_guard<std::mutex> sbguard1(my_mutex1);
		//std::lock_guard<std::mutex> sbguard2(my_mutex2);

		my_mutex2.lock();  //
		my_mutex1.lock();
		if (!msgRecvQueue.empty())
		
			//消息不为空
			int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();//移除第一个元素。但不返回;
			my_mutex2.unlock();
			my_mutex1.unlock(); //所有分支都必须有unlock()
			return true;
		
		my_mutex2.unlock();
		my_mutex1.unlock();
		return false;
	
	//把数据从消息队列取出的线程
	void outMsgRecvQueue()
	
		int command = 0;
		for (int i = 0; i < 10000; i++)
		
			bool result = outMsgLULProc(command);

			if (result == true)
			
				cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
				//处理数据
			
			else
			
				//消息队列为空
				cout << "inMsgRecvQueue()执行,但目前消息队列中为空!" << i << endl;
			
		
		cout << "end!" << endl;
	

private:
	std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。
	std::mutex my_mutex1;//创建一个互斥量(一把锁)
	std::mutex my_mutex2;//创建一个互斥量
;

int main()

	A myobja;

	std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);//第二个参数,引用,才能保证线程里用的是同一个对象
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myOutMsgObj.join();
	myInMsgObj.join();

	cout << "主线程执行!" << endl;

	return 0;

3.2 死锁的一般解决方案

只要保证这两个互斥量上锁的顺序一致就不会死锁

3.3 std::lock()函数模板

用来处理多个互斥量的时候才出场

能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限);它不存在这种因为多个线程中因为锁的顺序问题导致死锁的风险问题;

std::lock():如果互斥量中有一个没有锁住,它就在那等着,等所有互斥量都锁住,它才能往下走(返回);要么两个互斥量都锁柱,要么两个互斥量都没锁住,如果只锁了一个,另外一个没有锁成功,则它立即把已经锁住的解锁。实例代码如下:

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>

using namespace std;

//准备用成员函数作为线程函数的方法写线程
class A

public:
	//把收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	
		for (int i = 0; i < 10000; i++)
		
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

			std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了.lock()

			msgRecvQueue.push_back(i); //假设这个数字i就是收到的命令,直接弄到消息队列里边来;
			my_mutex2.unlock();
			my_mutex1.unlock();
		
	

	bool outMsgLULProc(int &command)
	
		std::lock(my_mutex1, my_mutex2);
		if (!msgRecvQueue.empty())
		
			//消息不为空
			int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();//移除第一个元素。但不返回;
			my_mutex2.unlock();
			my_mutex1.unlock(); //所有分支都必须有unlock()
			return true;
		
		my_mutex2.unlock();
		my_mutex1.unlock();
		return false;
	
	//把数据从消息队列取出的线程
	void outMsgRecvQueue()
	
		int command = 0;
		for (int i = 0; i < 10000; i++)
		
			bool result = outMsgLULProc(command);

			if (result == true)
			
				cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
				//处理数据
			
			else
			
				//消息队列为空
				cout << "inMsgRecvQueue()执行,但目前消息队列中为空!" << i << endl;
			
		
		cout << "end!" << endl;
	

private:
	std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。
	std::mutex my_mutex1;//创建一个互斥量(一把锁)
	std::mutex my_mutex2;//创建一个互斥量
;

int main()

	A myobja;

	std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);//第二个参数,引用,才能保证线程里用的是同一个对象
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myOutMsgObj.join();
	myInMsgObj.join();

	cout << "主线程执行!" << endl;

	return 0;

3.4 std::lock_guard()的std::adopt_lock参数

std::adopt_lock是个结构体对象,起一个标志作用:就是表示这个互斥量已经lock(),不需要在std::lock_guard<std::mutex>构造函数里再对mutex对象进行再次lock()了。实例代码如下:

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>

using namespace std;

//准备用成员函数作为线程函数的方法写线程
class A

public:
	//把收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	
		for (int i = 0; i < 10000; i++)
		
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

			std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了.lock()

			std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
			std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);

			msgRecvQueue.push_back(i); //假设这个数字i就是收到的命令,直接弄到消息队列里边来;

		
	

	bool outMsgLULProc(int &command)
	
		std::lock(my_mutex1, my_mutex2);

		std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
		std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);

		if (!msgRecvQueue.empty())
		
			//消息不为空
			int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();//移除第一个元素。但不返回;

			return true;
		

		return false;
	
	//把数据从消息队列取出的线程
	void outMsgRecvQueue()
	
		int command = 0;
		for (int i = 0; i < 10000; i++)
		
			bool result = outMsgLULProc(command);

			if (result == true)
			
				cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
				//处理数据
			
			else
			
				//消息队列为空
				cout << "inMsgRecvQueue()执行,但目前消息队列中为空!" << i << endl;
			
		
		cout << "end!" << endl;
	

private:
	std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。
	std::mutex my_mutex1;//创建一个互斥量(一把锁)
	std::mutex my_mutex2;//创建一个互斥量
;

int main()

	A myobja;

	std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);//第二个参数,引用,才能保证线程里用的是同一个对象
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myOutMsgObj.join();
	myInMsgObj.join();

	cout << "主线程执行!" << endl;

	return 0;

总结:std::lock()一次锁定多个互斥量;谨慎使用(建议一个一个锁),因为不同的互斥量控制不同的共享数据,两个互斥量在一起的情况不多见。

注:该文是C++11并发多线程视频教程笔记,详情学习:https://study.163.com/course/courseMain.htm?courseId=1006067356

线程同步与互斥详解(代码片段)

线程同步与互斥文章目录线程同步与互斥线程互斥进程线程间的互斥相关背景概念互斥量的接口互斥量实现原理可重入和线程安全常见的线程不安全的情况常见的线程安全情况常见锁概念死锁死锁四个必要条件避免死锁避免死锁... 查看详情

linxu多线程(进程与线程区别,互斥同步)(代码片段)

互斥同步线程线程概念线程优点线程缺点线程异常Linux进程VS线程概念关系线程控制创建线程线程ID及进程地址空间布局线程终止线程等待线程分离互斥背景知识互斥量mutex互斥条件互斥量的接口实例运用(售票系统)互斥... 查看详情

linux多线程——互斥和同步(代码片段)

目录一.线程互斥    1.1相关概念    1.2互斥量mutex    1.3互斥量的接口    1.4总结    1.5互斥锁实现原理(锁的原理)二.可重入函数和线程安全    2.1概念三.死锁         3.1概念    3.2死锁的必要条件        3.... 查看详情

[linux]linux多线程详解(代码片段)

目录1.线程概念1.1什么是线程1.2从操作系统看线程1.3线程的分类1.4线程的优缺点2.线程控制2.1线程创建2.2线程终止2.3线程等待2.4线程分离3.线程安全3.1线程不安全的现象3.1如何解决--互斥锁3.1.1互斥锁原理3.1.2互斥锁接口3.2死锁3.2.1... 查看详情

linux多线程(代码片段)

线程一、Linux线程概念1、什么是线程2、原生线程库pthread3、线程的优点4、线程的缺点5、线程异常6、线程的用途二、Linux进程VS线程1、进程和线程2、进程的多个线程共享3、进程和线程的区别三、POSIX线程库1、线程创建2、线程等... 查看详情

linux多线程(代码片段)

线程一、Linux线程概念1、什么是线程2、原生线程库pthread3、线程的优点4、线程的缺点5、线程异常6、线程的用途二、Linux进程VS线程1、进程和线程2、进程的多个线程共享3、进程和线程的区别三、POSIX线程库1、线程创建2、线程等... 查看详情

多线程(上)(代码片段)

文章目录Linux线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程关于进程线程的问题Linux线程控制POSIX线程库创建线程进程ID和线程ID线程ID及进程地址空间布局线程等待为什么需要线程等待ÿ... 查看详情

多线程详解(代码片段)

多线程4.4题外话5线程的死锁问题5.1概念5.1.1死锁的理解5.1.2说明5.1.3举例5.2解决方法5.3演示线程的死锁问题6.JDK5.0新增解决线程安全问题6.1概念6.2步骤6.3举例6.4synchronized与Lock的异同?(面试题)7.线程的通信7.1线程通信的例... 查看详情

[linux]linux多线程详解(代码片段)

目录1.线程概念1.1什么是线程1.2从操作系统看线程1.3线程的分类1.4线程的优缺点2.线程控制2.1线程创建2.2线程终止2.3线程等待2.4线程分离3.线程安全3.1线程不安全的现象3.1如何解决--互斥锁3.1.1互斥锁原理3.1.2互斥锁接口3.2死锁3.2.1... 查看详情

linux入门多线程(线程概念生产者消费者模型消息队列线程池)万字解说(代码片段)

目录1️⃣线程概念什么是线程线程的优点线程的缺点线程异常线程异常Linux进程VS线程2️⃣线程控制创建线程获取线程的id线程终止等待线程线程分离3️⃣线程互斥进程线程间的互斥概念互斥量互斥量的接口互斥量的实现原理研... 查看详情

详解c++11多线程(代码片段)

c++的多线程可以充分利用计算机资源,提高代码运行效率。在这里总结了一些多线程应用过程中的基本概念和用法。一、进程与线程进程是资源分配和调度的一个独立单位。而线程是进程的一个实体,是CPU调度和分派的基本单位... 查看详情

linux入门多线程(线程概念生产者消费者模型消息队列线程池)万字解说(代码片段)

目录1️⃣线程概念什么是线程线程的优点线程的缺点线程异常线程异常Linux进程VS线程2️⃣线程控制创建线程获取线程的id线程终止等待线程线程分离3️⃣线程互斥进程线程间的互斥概念互斥量互斥量的接口互斥量的实现原理研... 查看详情

linux多线程(代码片段)

多线程一、线程是什么二、线程的优缺点线程的优点线程的缺点线程异常线程用途进程和线程对比三、线程的控制POSIX线程库线程ID和进程ID线程ID及进程地址空间布局线程终止线程等待线程分离三、线程互斥进程线程间的互斥互... 查看详情

多线程-数据共享问题(代码片段)

...mutex)互斥量:是个类对象(可以理解为一把锁),多个线程尝试用lock()成员函数来加锁这把锁,只有一个线程能锁定成功(成功的标志是lock()函数能够返回,返回不了说明没有锁成功)2、死锁死锁:一般是两个或两个以上的互... 查看详情

死锁及预防(代码片段)

...件死锁(死锁最初概念是在多进程模式下提出的,这里以线程来描述是同一个意思)是多线程并发程序中的一个难题,要产生死锁需要满足下面4个条件:有限资源互斥条件:资源只能被线程独占,只有被释放后才能被其它线程... 查看详情

万字详解linux系列多线程(上)(代码片段)

文章目录前言一、线程1.概念2.优点3.缺点4.线程异常二、进程与线程1.进程和线程2.进程的多个线程共享三、线程控制1.线程创建2.线程查看命令行查看用函数查看3.线程等待参数thread参数retval4.进程退出returnpthread_exitpthread_cancel四... 查看详情

多线程编程之线程死锁问题

在多线程编程中,除了要解决数据访问的同步与互斥之外,还需要解决的重要问题就是多线程的死锁问题。所谓死锁:是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外部处理... 查看详情

11.8-全栈java笔记:死锁及解决方案

 死锁的概念“死锁”指的是:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。         &nb... 查看详情