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)的基本概念

1.我们为什么需要互斥量

这就涉及到了共享资源的概念,例如:买票这一行为,(票就是我们的共享资源)
假设有两个窗口 12号窗口(可以看成是两个"售票"子线程),
这时有两人同时购买从上海到北京的票,同样挑中了A1座,如果这时不对这类
行为做任何处理,就会造成两个人同时买到A1票的错误反馈。

保护共享数据,操作时,某个线程 用代码把共享数据锁住->操作数据->解锁;其他想操作共享数据的线程必须等待解锁->锁定住->操作->解锁;
以上种种引出 “互斥量”

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

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

二、互斥量的用法

2.1 lock(),unlock()

mutex互斥量是一个类,这个类有一个lock()方法,和一个unlock()方法(成员函数)。

(1)引入头文件

#include <mutex>
using namespace std;

(2)lock()和unlock()

lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

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

(3)测试代码

// project6.cpp : 定义控制台应用程序的入口点。
//

//网络游戏服务器 
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
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);//假设这个数字i就是服务器收到的命令,直接弄到消息队列里面来
			my_mutex.unlock();//成对使用,在写的时候保护一下
		}//从后塞
		return;
	}

	把数据从消息队列中取出的线程
	//void outMsgRecvQueue()
	//{
	//	for (int i = 0; i < 100000; ++i)
	//	{
	//		if (!msgRecvQueue.empty())
	//		{
	//			//消息不为空
	//			//front() 返回第一个元素,但不检查元素是否存在;
	//			int command = msgRecvQueue.front();//从头取
	//			msgRecvQueue.pop_front();//移除第一个元素,但不返回
	//									 //这里就要考虑处理数据...
	//									 //......
	//		}
	//		else
	//		{
	//			//消息队列为空
	//			cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
	//		}
	//	}
	//	cout << "end" << endl;
	//}

	//方便处理共享数据
	bool outMsgLULProc(int &command)//引用
	{
		//必须是同一个互斥量的锁与不锁
		my_mutex.lock();
		//消息队列不为空取命令(判断空不空也是对共享数据的访问,所以要加锁)
		if (!msgRecvQueue.empty())
		{
			//消息不为空
			//front() 返回第一个元素,但不检查元素是否存在;
			command = msgRecvQueue.front();//从头取
			msgRecvQueue.pop_front();//移除第一个元素,但不返回
			my_mutex.unlock();//一一对应 一次lock 一次unlock
			return true;
			//两个出口必然需要两个unlock
			//每一个分支往外退就得有一个unlock
		}
		my_mutex.unlock();//解锁
		return false;//为空返回失败
	}

	//把数据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool results = outMsgLULProc(command);
			if (results == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//.....
			}
			else
			{
				//消息队列为空
				cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	//共享数据
	std::list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给服务器发送过来的命令。
	std::mutex my_mutex;//创建了一个互斥量
};
int main()
{
	A myobja;
	//类成员函数
	//第二个参数是 引用,才能保证线程里用的是同一个对象myobja。
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	//分析问题:
	//inMsgRecvQueue 不断的往容器里面写数据
	//outMsgRecvQueue 不断往容器里面读数据
	//两个线程有读有写,若是完全不控制随意运行就会有问题
	//就得按顺序按规矩去访问共享数据

	//代码化解决问题:
	//引入一个C++解决多线程保护共享数据问题的第一个概念“互斥量”。
	myOutnMsgObj.join();
	myInMsgObj.join();
	return 0;
}

(4)运行结果

在这里插入图片描述

(5)分析总结

  • 有lock,忘记unlock的问题,非常难排查;
  • 为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板;忘记了加unlock,此类模板会帮我们unlock
  • 就像智能指针(unique_ptr()):忘记释放内存,会帮我们释放(保姆)

2.2 std::lock_quard类模板

std::lock_guard类模板:直接取代lock()和unlock();
即使用了lock_guard后,再也不能使用lock()和unlock()了

(1)测试代码


//网络游戏服务器 
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//可以把需要加锁的代码提取成一个函数,方便加锁

class A {
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			{//让其再每一次for之后都析构(有意义:在每一次for结束之前还有很长的一段处理代码)
				std::lock_guard<std::mutex> sbguard(my_mutex);
				//my_mutex.lock();
				msgRecvQueue.push_back(i);//假设这个数字i就是服务器收到的命令,直接弄到消息队列里面来
			}//提前解锁,提前结束sbguard的生命周期
			//my_mutex.unlock();//成对使用,在写的时候保护一下
			//从后塞

			//......
			//其他处理代码;(很长很久)
		}//如果不提前就会在,就会在这里(所有的100000次之后)结束生命周期
		 
		return;
	}

	//方便处理共享数据
	bool outMsgLULProc(int &command)//引用
	{
		//std::lock_guard :类模板
		//std::mutex 类型
		//sbguard : 对象名
		//my_mutex :互斥量
		std::lock_guard<std::mutex> sbguard(my_mutex);
		//什么工作原理?
		//1.sbguard生成这样的对象,调用这个类的构造函数(在这个类的构造函数里就执行了lock())
			//lock_guard构造函数里执行了mutex::lock();
		//2.sbguard是局部对象return 退出函数时局部对象超过作用域会析构
			//lock_guard析构函数里执行了mutex::unlock();
		//3.可以用{}(作用域)的方式来提前结束sbguard的生命周期;提前解锁,不必等到return再解锁


		//必须是同一个互斥量的锁与不锁
		//my_mutex.lock();
		//消息队列不为空取命令(判断空不空也是对共享数据的访问,所以要加锁)
		if (!msgRecvQueue.empty())
		{
			//消息不为空
			//front() 返回第一个元素,但不检查元素是否存在;
			command = msgRecvQueue.front();//从头取
			msgRecvQueue.pop_front();//移除第一个元素,但不返回
			//my_mutex.unlock();//一一对应 一次lock 一次unlock
			return true;
			//两个出口必然需要两个unlock
			//每一个分支往外退就得有一个unlock
		}
		//my_mutex.unlock();//解锁
		return false;//为空返回失败
	}

	//把数据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool results = outMsgLULProc(command);
			if (results == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//.....
			}
			else
			{
				//消息队列为空
				cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	//共享数据
	std::list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给服务器发送过来的命令。
	std::mutex my_mutex;//创建了一个互斥量
};
int main()
{
	A myobja;
	//类成员函数
	//第二个参数是 引用,才能保证线程里用的是同一个对象myobja。
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);


	myOutnMsgObj.join();
	myInMsgObj.join();

	return 0;
}

(2)运行结果图

在这里插入图片描述

(3)总结

  • 一个互斥量就是一把"锁头"

三、死锁

例如:
生活中
张三:站在北京等李四来才走,但是一个人时不动
李四:站在上海等张三来才走,但是一个人时不动
这就造成了互相等待,互相不行动
在C++中
假设现在有两把锁(死锁这个问题 是由至少两个“锁头”也就是两个互斥量才能产生);金锁(JinLock),银锁(YinLock);
两个线程A,B
(1)线程A执行的时候,这个线程先锁 金锁,把金锁lock()成功了,然后它去lock银锁。。。
这时出现了上下文切换
(2)(切换到了线程B)线程B执行了,这个线程先锁 银锁,因为银锁还没有被锁,所以银锁会lock()成功,接着线程B要去lock金锁…
此时此刻,死锁就产生了;
(3)线程A因为拿不到银锁头,流程走不下去(所有后边代码有unlock金锁头的但是流程走不下去,所以金锁头解不开);
(4)线程B因为拿不到金锁头,流程走不下去(所有后边代码有unlock银锁头的但是流程走不下去,所以银锁头解不开);
这样两个线程都只能停在这里,A等B,B等A;
这就是死锁。

3.1 死锁演示

(1)代码演示

//网络游戏服务器 
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//可以把需要加锁的代码提取成一个函数,方便加锁

class A {
public:
	//inMsgRecvQueue 先锁1后锁2
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			
			my_mutex1.lock();//实际工程这两个锁头不一定紧挨着,可能他们需要保护不同的数据共享块
			my_mutex2.lock();
			msgRecvQueue.push_back(i);//假设这个数字i就是服务器收到的命令,直接弄到消息队列里面来
		
			my_mutex2.unlock();
			my_mutex1.unlock();
			
		}

		return;
	}

	//方便处理共享数据
	bool outMsgLULProc(int &command)//引用
	{
		
		//死锁:这里锁的顺序相反
		my_mutex2.lock();
		my_mutex1.lock();
		//消息队列不为空取命令(判断空不空也是对共享数据的访问,所以要加锁)
		if (!msgRecvQueue.empty())
		{
			//消息不为空
			//front() 返回第一个元素,但不检查元素是否存在;
			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 results = outMsgLULProc(command);
			if (results == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//.....
			}
			else
			{
				//消息队列为空
				cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	//共享数据
	std::list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给服务器发送过来的命令。
	std::mutex my_mutex1;//创建了一个互斥量
	std::mutex my_mutex2;//创建了一个互斥量(两把锁头)

};
int main()
{
	A myobja;
	//类成员函数
	//第二个参数是 引用,才能保证线程里用的是同一个对象myobja。
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);


	myOutnMsgObj.join();
	myInMsgObj.join();

	return 0;
}

(2)运行结果图

在这里插入图片描述

unlock的顺序不重要

3.2 死锁的一般解决方案

(1) 只要保证这两个互斥量上锁的顺序一致就不会死锁【代码+运行结果】

//网络游戏服务器 
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//可以把需要加锁的代码提取成一个函数,方便加锁

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

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

			my_mutex2.unlock();
			my_mutex1.unlock();

		}

		return;
	}

	//方便处理共享数据
	bool outMsgLULProc(int &command)//引用
	{

		//死锁:这里锁的顺序相反
		my_mutex1.lock();
		my_mutex2.lock();
		//消息队列不为空取命令(判断空不空也是对共享数据的访问,所以要加锁)
		if (!msgRecvQueue.empty())
		{
			//消息不为空
			//front() 返回第一个元素,但不检查元素是否存在;
			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 results = outMsgLULProc(command);
			if (results == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//.....
			}
			else
			{
				//消息队列为空
				cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	//共享数据
	std::list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给服务器发送过来的命令。
	std::mutex my_mutex1;//创建了一个互斥量
	std::mutex my_mutex2;//创建了一个互斥量(两把锁头)

};
int main()
{
	A myobja;
	//类成员函数
	//第二个参数是 引用,才能保证线程里用的是同一个对象myobja。
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);


	myOutnMsgObj.join();
	myInMsgObj.join();

	return 0;
}

在这里插入图片描述

3.3 std::lock()函数模板

std::lock()函数模板:用来处理多个互斥量
功能:(同时)一次锁住两个或者两个以上的互斥量(至少两个,多了不行,1个也不行);
它不存在这种在多个线程中 因为锁的顺序问题导致死锁的风险问题;
std::lock():如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回);
要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没锁成功,则它立即把已经锁住的解锁。
保证不会出现死锁,不管锁的顺序。

(1)代码测试

//网络游戏服务器 
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//可以把需要加锁的代码提取成一个函数,方便加锁

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

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

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

			//前面锁住2个,后面就得给两个解锁
			my_mutex2.unlock();
			my_mutex1.unlock();

		}

		return;
	}

	//方便处理共享数据
	bool outMsgLULProc(int &command)//引用
	{

		std::lock(my_mutex1, my_mutex2);
		//消息队列不为空取命令(判断空不空也是对共享数据的访问,所以要加锁)
		if (!msgRecvQueue.empty())
		{
			//消息不为空
			//front() 返回第一个元素,但不检查元素是否存在;
			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 results = outMsgLULProc(command);
			if (results == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//.....
			}
			else
			{
				//消息队列为空
				cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	//共享数据
	std::list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给服务器发送过来的命令。
	std::mutex my_mutex1;//创建了一个互斥量
	std::mutex my_mutex2;//创建了一个互斥量(两把锁头)

};
int main()
{
	A myobja;
	//类成员函数
	//第二个参数是 引用,才能保证线程里用的是同一个对象myobja。
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	
	myOutnMsgObj.join();
	myInMsgObj.join();

	return 0;
}

3.4 std::lcok_guard的std::adopt_lock参数

std::adopt_lock是个结构体对象,起一个标记作用:
作用就是表示这个互斥量已经lock()了,不需要在std::lock_guard< std::mutex >构造函数里面再对mutex对象进行lock()了;
std::lock_guard< std::mutex >析构时功能是正常的。

(1)代码测试

//网络游戏服务器 
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//可以把需要加锁的代码提取成一个函数,方便加锁

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

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

			//std::adopt_lock 让sqm1对象构造的时候,不调用 mutex.lock() 这就避免了冲突
			//这样在析构的时候,也能帮忙调用unlock,而无需我们手动去写
			std::lock_guard<std::mutex> sqm1(my_mutex1, std::adopt_lock);
			std::lock_guard<std::mutex> sqm2(my_mutex2, std::adopt_lock);

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

			//前面锁住2个,后面就得给两个解锁
			//my_mutex2.unlock();
			//my_mutex1.unlock();

		}

		return;
	}

	//方便处理共享数据
	bool outMsgLULProc(int &command)//引用
	{

		std::lock(my_mutex1, my_mutex2);
		//消息队列不为空取命令(判断空不空也是对共享数据的访问,所以要加锁)
		std::lock_guard<std::mutex> sqm1(my_mutex1, std::adopt_lock);
		std::lock_guard<std::mutex> sqm2(my_mutex2, std::adopt_lock);
		if (!msgRecvQueue.empty())
		{
			//消息不为空
			//front() 返回第一个元素,但不检查元素是否存在;
			command = msgRecvQueue.front();//从头取
			msgRecvQueue.pop_front();//移除第一个元素,但不返回
			
			return true;

		}
		
		return false;//为空返回失败
	}

	//把数据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool results = outMsgLULProc(command);
			if (results == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//.....
			}
			else
			{
				//消息队列为空
				cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	//共享数据
	std::list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给服务器发送过来的命令。
	std::mutex my_mutex1;//创建了一个互斥量
	std::mutex my_mutex2;//创建了一个互斥量(两把锁头)

};
int main()
{
	A myobja;
	//类成员函数
	//第二个参数是 引用,才能保证线程里用的是同一个对象myobja。
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsgObj.join();
	myInMsgObj.join();
	return 0;
}

(2)运行结果

在这里插入图片描述

3.5 总结

  • std::lock():一次锁定多个互斥量;谨慎使用(建议一个一个锁,因为一般,多个互斥量有多个要保护的东西(保护不同的东西),很少会挨在一起,同时锁定)
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-03 12:51:42  更:2021-12-03 12:53:29 
 
开发: 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 11:13:12-

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