IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++11多线程编程(五)互斥量概念,用法,死锁演示集解决详解 -> 正文阅读

[C++知识库]c++11多线程编程(五)互斥量概念,用法,死锁演示集解决详解

一、互斥量(mutex)概念的基本概念

  • 互斥量是一个类对象,理解成一把锁,多个线程尝试用lock()成员函数来尝试加锁头,只有一个线程能够锁定成功,成功的标志是返回,如果没有所成功,那么这个线程的执行流程就会卡在lock()这里不断尝试去锁这把锁;
  • 互斥量使用需要小心:只保护需要保护的数据,也必须保护全(保护多了影响效率,保护少了达不到保护效果),操作完以后要把锁解开,别人才能使用lock继续执行;

二、互斥量的用法

1.lock(),unlock()

  • 步骤:先lock(),操作共享数据,再unlock();
  • lock()和unlock()要成对使用,每调用一次lock(),必须调用一次unlock();
  • 有lock()忘记unlock()一般很难排查;
  • 为了防止大家忘记unlock(),引入了一个叫std::lock_guard()的类模板;你忘记unlock(),我帮你unlock();
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>


using namespace std;

//成员函数作为线程函数的方法来写线程

class A
{
public:
	//线程一:从玩家收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			//如果没有锁,会不定时崩溃
			my_mutex.lock();
			msgRecvQueue.push_back(i);
			my_mutex.unlock();
		}
	}
	bool outMsgLULProc(int &command) 
	{
		my_mutex.lock();
		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			my_mutex.unlock();
			return true;

		}
		my_mutex.unlock();
		return false;
	}
	//线程二:把数据从消息队列取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true) 
			{
				cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
			}
			else 
			{
				cout << "outMsgRecvQueue()执行,队列为空!" << endl;
			}
		}
	}

private:
	//容器:专用于代表玩家发送过来的命令
	list<int> msgRecvQueue;
	std::mutex my_mutex; //创建一个互斥量


};

int main()
{
	//数据共享:网络游戏服务器
	//有两个自己创建的线程
	//一个线程:收集玩家发来的命令(简化问题:用一个数字代表),将命令数据写入一个队列
	//一个线程:从队列中取出玩家发送来的命令,解析,执行玩家要干的动作
	//使用list:容器(和vector内部的实现手法是不一样的)
	//频繁的按照顺序插入和删除数据时list效率高
	//对于随意插入和删除数据时vector效率高
	A myobja;
	//第二个参数时引用,才能保证线程里,用的时同一个对象
	thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgobj(&A::inMsgRecvQueue, &myobja);

	//操作的时候用代码把共享数据锁住,其他想操作共享数据的线程必须等待
	//锁住后操作数据、解锁
	//解锁后其他数据锁住,操作数据、解锁
	myOutMsgobj.join();
	myInMsgobj.join();
	Sleep(10);
	return 0;
}

2.std::lock_guard类模板

  • std::lock_guard类模板:直接取代lock()和unlock();也就是说,使用了lock_guard()后就不能再使用lock()和unlock();
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>


using namespace std;

//成员函数作为线程函数的方法来写线程

class A
{
public:
	//线程一:从玩家收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			//如果没有锁,会不定时崩溃
			my_mutex.lock();
			msgRecvQueue.push_back(i);
			my_mutex.unlock();
		}
	}
	bool outMsgLULProc(int &command) 
	{
		//有lock_guard()后就不需要再用lock()和unlock()
		//lock_guard() 构造函数执行了一次my_mutex.lock()
		//析构函数中执行了一次my_mutex.unlock()
		//但是解锁只能在析构(函数返回)时解锁,所以不是很灵活
		//可以使用大括号,在大括号中写lock_guard(),这样出了作用域就会自动析构,进行解锁
		std::lock_guard<std::mutex> sbguard(my_mutex);
		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return true;
		}
		return false;
	}
	//线程二:把数据从消息队列取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true) 
			{
				cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
			}
			else 
			{
				cout << "outMsgRecvQueue()执行,队列为空!" << endl;
			}
		}
	}

private:
	//容器:专用于代表玩家发送过来的命令
	list<int> msgRecvQueue;
	std::mutex my_mutex; //创建一个互斥量


};

int main()
{
	//数据共享:网络游戏服务器
	//有两个自己创建的线程
	//一个线程:收集玩家发来的命令(简化问题:用一个数字代表),将命令数据写入一个队列
	//一个线程:从队列中取出玩家发送来的命令,解析,执行玩家要干的动作
	//使用list:容器(和vector内部的实现手法是不一样的)
	//频繁的按照顺序插入和删除数据时list效率高
	//对于随意插入和删除数据时vector效率高
	A myobja;
	//第二个参数时引用,才能保证线程里,用的时同一个对象
	thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgobj(&A::inMsgRecvQueue, &myobja);

	//操作的时候用代码把共享数据锁住,其他想操作共享数据的线程必须等待
	//锁住后操作数据、解锁
	//解锁后其他数据锁住,操作数据、解锁
	myOutMsgobj.join();
	myInMsgobj.join();
	Sleep(10);
	return 0;
}

三、死锁

  • 现实生活中的死锁:张三站在北京等李四,不动,李四站在深圳等张三,不动;
  • c++中的死锁(至少有两把锁):比如有两把锁,锁一,锁二,两个线程,线程A,线程B;(业务:需要把两把锁都锁上)
  • (1)线程A执行的时候,这个线程先锁锁一;然后去锁锁二;
  • (2)出现了上下文切换,线程A被切换走了,线程B开始执行,这个线程先锁锁二成功;然后线程B去锁锁一;
  • 此时此刻,死锁就发生了
  • (3)线程A锁不了锁二,流程走不下去,所以锁一解不开;
  • (4)线程B锁不了锁一,流程走不下去,所以锁二解不开;

1.死锁演示

#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>


using namespace std;

//成员函数作为线程函数的方法来写线程

class A
{
public:
	//线程一:从玩家收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			//先锁一,后锁二
			my_mutex1.lock();
			//执行.......
			my_mutex2.lock();
			msgRecvQueue.push_back(i);
			//unlock的顺序无所谓
			my_mutex1.unlock();
			my_mutex2.unlock();
			
		}
	}
	bool outMsgLULProc(int& command)
	{
		//先锁二,后锁一
		my_mutex2.lock();
		//执行.......
		my_mutex1.lock();
		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			//unlock的顺序无所谓
			my_mutex1.unlock();
			my_mutex2.unlock();
			return true;
		}
		//unlock的顺序无所谓
		my_mutex1.unlock();
		my_mutex2.unlock();
		return false;
	}
	//线程二:把数据从消息队列取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
			}
			else
			{
				cout << "outMsgRecvQueue()执行,队列为空!" << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;
	std::mutex my_mutex1; //创建第一个互斥量
	std::mutex my_mutex2; //创建第二个互斥量
};

int main()
{
	
	A myobja;
	
	thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgobj(&A::inMsgRecvQueue, &myobja);

	
	myOutMsgobj.join();
	myInMsgobj.join();
	Sleep(10);
	return 0;
}

2.死锁的一般解决方案

  • 死锁产生的原因:锁的顺序不一样,只要保证两个互斥量上锁的顺序一样,就不会产生死锁;
  • 使用lock_guard()也是相同的;
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>


using namespace std;

//成员函数作为线程函数的方法来写线程

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

			std::lock_guard<std::mutex> sbguard1(my_mutex1);
			std::lock_guard<std::mutex> sbguard2(my_mutex2);
			msgRecvQueue.push_back(i);
			//unlock的顺序无所谓
	;
			
		}
	}
	bool outMsgLULProc(int& command)
	{
		std::lock_guard<std::mutex> sbguard1(my_mutex1);
		std::lock_guard<std::mutex> sbguard2(my_mutex2);
		
		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return true;
		}
		return false;
	}
	//线程二:把数据从消息队列取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
			}
			else
			{
				cout << "outMsgRecvQueue()执行,队列为空!" << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;
	std::mutex my_mutex1; //创建第一个互斥量
	std::mutex my_mutex2; //创建第二个互斥量
};

int main()
{
	
	A myobja;
	
	thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgobj(&A::inMsgRecvQueue, &myobja);

	
	myOutMsgobj.join();
	myInMsgobj.join();
	Sleep(10);
	return 0;
}

3.std::lock()函数模板

-用于处理多个互斥量;
一次锁住两个或者两个以上的互斥量(至少两个);(同时锁住多个互斥量的情况比较少见);

  • 不存在在多线程中,因为锁的顺序导致死锁的风险问题;
  • std::lock():如果互斥量中有一个每锁住,他就会释放自己锁住的,然后就等在那里,等所有互斥量都锁住,才往下走;
  • 要么两个互斥量都锁住或者两个互斥量都释放;如果只锁一个,另外一个没成功,则立即释放锁住的;
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>


using namespace std;

//成员函数作为线程函数的方法来写线程

class A
{
public:
	//线程一:从玩家收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			//相当于每个互斥两都调用了lock();
			//可以有多个参数
			//必须对应解锁
			std::lock(my_mutex1, my_mutex2);
			msgRecvQueue.push_back(i);
			//unlock的顺序无所谓
			my_mutex1.unlock();
			my_mutex2.unlock();

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

		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			my_mutex1.unlock();
			my_mutex2.unlock();
			return true;
		}
		my_mutex1.unlock();
		my_mutex2.unlock();
		return false;
	}
	//线程二:把数据从消息队列取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
			}
			else
			{
				cout << "outMsgRecvQueue()执行,队列为空!" << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;
	std::mutex my_mutex1; //创建第一个互斥量
	std::mutex my_mutex2; //创建第二个互斥量
};

int main()
{

	A myobja;

	thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgobj(&A::inMsgRecvQueue, &myobja);


	myOutMsgobj.join();
	myInMsgobj.join();
	Sleep(10);
	return 0;
}

4.std::lock_guard的std::adopt_lock参数

  • std::adopt_lock 可以使std::lock_guard()在构造函数不进行lock();
  • std::adopt_lock是一个结构体对象,起一个标记作用,表示互斥量已经进行lock了,不需要在构造函数里面在对其进行lock;
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>


using namespace std;

//成员函数作为线程函数的方法来写线程

class A
{
public:
	//线程一:从玩家收到的消息入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			
			std::lock(my_mutex1, my_mutex2);
			//std::adopt_lock 在构造函数不进行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);
	

		}
	}
	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 < 100000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
			}
			else
			{
				cout << "outMsgRecvQueue()执行,队列为空!" << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;
	std::mutex my_mutex1; //创建第一个互斥量
	std::mutex my_mutex2; //创建第二个互斥量
};

int main()
{

	A myobja;

	thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgobj(&A::inMsgRecvQueue, &myobja);


	myOutMsgobj.join();
	myInMsgobj.join();
	Sleep(10);
	return 0;
}
  • 建议一个一个的锁,尽量不要同时lock();同时lock()也不常见;
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:43:52  更:2022-03-06 12:46:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:07:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码