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++知识库]互斥量概念、用法、死锁演示及解决详解

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

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

互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功(成功的标志是lock()函数返回),如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。

二、互斥量的用法

包含mutex 头文件(好像也可以不加)

2.1 lock(),unlock()

步骤:1.lock(),2.操作共享数据,3.unlock()。
lock()和unlock()要成对使用,有lock()必然要有unlock,每调用一次lock(),必然应该调用一次unlock();
不应该也不允许调用1次lock()却调用了2次unlock(),也不允许调用2次lock()却调用1次unlock(),这些非对称数量的调用都会导致代码的不稳定甚至崩溃

#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace  std;

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
			
			//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
			my_mutex.lock();
			msgReceiveQueue.push_back(i);
			my_mutex.unlock();
		}
		return;
	}

	bool outMsgLULProc(int &command) {
		my_mutex.lock();
		if (!msgReceiveQueue.empty())
		{
			//消息不为空,则取出数据
			command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
			msgReceiveQueue.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()执行,取出一个元素" << endl;
			}
			else {
				//消息队列为空
				cout << "目前消息队列为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}


private:
	list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
	mutex my_mutex;//创建了一个互斥量
};


int main()
{
	//用成员函数作为线程的方法来写线程
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutMsgObj.join();
	//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
	cout << "I Love China" << endl;//最后执行这句,整个进程退出

	return 0;
}
2.2 lock_guard类模板

lock_guard guard(myMutex);直接取代lock()和unlock(),也就是说用了lock_guard之后,再不能使用lock()和unlock()了
lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock()

#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace  std;

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

			//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
			{
				lock_guard<mutex>guard(my_mutex);
				msgReceiveQueue.push_back(i);
			}
		}
		return;
	}

	bool outMsgLULProc(int &command) {
		lock_guard<mutex>guard(my_mutex);
		if (!msgReceiveQueue.empty())
		{
			//消息不为空,则取出数据
			command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
			msgReceiveQueue.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()执行,取出一个元素" << endl;
			}
			else {
				//消息队列为空
				cout << "目前消息队列为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}


private:
	list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
	mutex my_mutex;//创建了一个互斥量
};


int main()
{
	//用成员函数作为线程的方法来写线程
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutMsgObj.join();
	//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
	cout << "I Love China" << endl;//最后执行这句,整个进程退出

	return 0;
}

三、死锁

3.1 死锁演示

死锁至少有两个互斥量mutex1,mutex2。

a.线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
b.线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.
c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。。。

#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace  std;

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

			//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
			{
				my_mutex1.lock();//实际工程这两个锁头不一定挨着,可能需要保护不同的数据共享块
				my_mutex2.lock();
				msgReceiveQueue.push_back(i);
				my_mutex2.unlock();
				my_mutex1.unlock();
			}

		}
		return;
	}

	bool outMsgLULProc(int &command) {
		my_mutex2.lock();//实际工程这两个锁头不一定挨着,可能需要保护不同的数据共享块
		my_mutex1.lock();
		if (!msgReceiveQueue.empty())
		{
			//消息不为空,则取出数据
			command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
			msgReceiveQueue.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()执行,取出一个元素" << endl;
			}
			else {
				//消息队列为空
				cout << "目前消息队列为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}

private:
	list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
	mutex my_mutex1;//创建了一个互斥量
	mutex my_mutex2;
};


int main()
{
	//用成员函数作为线程的方法来写线程
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutMsgObj.join();
	//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
	cout << "I Love China" << endl;//最后执行这句,整个进程退出

	return 0;
}
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace  std;

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

			//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
			{
				lock_guard<mutex>guard1(my_mutex1);
				lock_guard<mutex>guard2(my_mutex2);
				msgReceiveQueue.push_back(i);
			}

		}
		return;
	}

	bool outMsgLULProc(int &command) {
		lock_guard<mutex>guard2(my_mutex2);
		lock_guard<mutex>guard1(my_mutex1);
		if (!msgReceiveQueue.empty())
		{
			//消息不为空,则取出数据
			command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
			msgReceiveQueue.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()执行,取出一个元素" << endl;
			}
			else {
				//消息队列为空
				cout << "目前消息队列为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}

private:
	list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
	mutex my_mutex1;//创建了一个互斥量
	mutex my_mutex2;
};


int main()
{
	//用成员函数作为线程的方法来写线程
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutMsgObj.join();
	//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
	cout << "I Love China" << endl;//最后执行这句,整个进程退出

	return 0;
}

ps:理论上上面两段代码都会出现死锁现象,但实际并没有。。。

3.2 死锁的一般解决方案:

只要保证多个互斥量上锁的顺序一样就不会造成死锁。

3.3 std::lock()函数模板

std::lock(mutex1,mutex2……); 用来处理多个互斥量才出场
一次锁定多个互斥量(至少两个,多了不限,1个不行)一般这种情况很少。
不存在在多个线程中因为锁的顺序问题导致死锁的风险问题
如果互斥量中一个没锁住,它就等着,等所有互斥量都锁住,才能继续执行。
要么互斥量都锁住,要么都没锁住
如果只锁了一个,另一个没锁成功,则会把已经锁住的解锁

#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace  std;

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

			//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
			{
				lock(my_mutex1, my_mutex2);
				msgReceiveQueue.push_back(i);
				my_mutex2.unlock();
				my_mutex1.unlock();
			}

		}
		return;
	}

	bool outMsgLULProc(int &command) {
		lock(my_mutex1, my_mutex2);
		if (!msgReceiveQueue.empty())
		{
			//消息不为空,则取出数据
			command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
			msgReceiveQueue.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()执行,取出一个元素" << endl;
			}
			else {
				//消息队列为空
				cout << "目前消息队列为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}

private:
	list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
	mutex my_mutex1;//创建了一个互斥量
	mutex my_mutex2;
};


int main()
{
	//用成员函数作为线程的方法来写线程
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutMsgObj.join();
	//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
	cout << "I Love China" << endl;//最后执行这句,整个进程退出

	return 0;
}
3.4 std::lock_guard的std::adopt_lock参数

std::lock_guardstd::mutex my_guard(my_mutex,std::adopt_lock);
加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();
adopt_guard为结构体对象,起一个标记作用,表示这个互斥量已经lock(),不需要再std::lock_guardstdstd::mutex构造函数里对对象再次 lock()了。

#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace  std;

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

			//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
			{
				lock(my_mutex1, my_mutex2);

				lock_guard<mutex>guard1(my_mutex1,adopt_lock);
				lock_guard<mutex>guard2(my_mutex2,adopt_lock);

				msgReceiveQueue.push_back(i);
			}
		}
		return;
	}

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

		lock_guard<mutex>guard2(my_mutex2,adopt_lock);
		lock_guard<mutex>guard1(my_mutex1,adopt_lock);
		if (!msgReceiveQueue.empty())
		{
			//消息不为空,则取出数据
			command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
			msgReceiveQueue.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()执行,取出一个元素" << endl;
			}
			else {
				//消息队列为空
				cout << "目前消息队列为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}

private:
	list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
	mutex my_mutex1;//创建了一个互斥量
	mutex my_mutex2;
};


int main()
{
	//用成员函数作为线程的方法来写线程
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutMsgObj.join();
	//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
	cout << "I Love China" << endl;//最后执行这句,整个进程退出

	return 0;
}

总结:std::lock(); 一次锁定多个互斥量;谨慎使用(建议一个一个锁)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-22 20:23:18  更:2022-02-22 20:24:04 
 
开发: 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 6:49:50-

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