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

一、互斥量(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(); 一次锁定多个互斥量;谨慎使用(建议一个一个锁)