c++ 多线程编程 共享数据的保护即互斥量的用法

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;
}

2.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;
}

2.4 std::lock_guard()的std::adopt_lock参数
std::adopt_lock是个结构体对象,起一个标志作用:就是表示这个互斥量已经lock(),不需要在std::lock_guardstd::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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值