C++ 并发与多线程(四)

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

保护共享号数据,某个线程操作时需要把共享数据锁住,然后操作数据,最后解锁。
其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁。

互斥量类对象,理解为一把锁,多个线程尝试用lock函数来加锁这个锁头,只有一个线程能锁定成功,如果没有锁成功,则一直卡在lock函数直到有线程锁定成功

互斥量使用要小心,保护多了或少了都不好。

2.互斥量的用法

2.1 lock()和unlock()

当不使用lock和unlock时,同时读写绘出错
在这里插入图片描述
当我们加入lock和unlock时

// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
using namespace std;


//vector<int>g_v = { 1,2,3 };
//
//void Myprint(int inum)
//{
//	cout << "myprint线程开始执行了,线程编号" << inum << endl;
//	cout << "myprint线程结束执行了,线程编号" << inum << endl;
//	cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;
//
//}



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


	bool outMsgLULproc(int &command)
	{
		my_mutex.lock();
		if (!msgRecvQueue.empty())
		{
		    command = msgRecvQueue.front();      //返回第一个元素但不检查元素是否存在
			msgRecvQueue.pop_front();            //移除第一个元素但不返回
			//处理数据。。。。。
			my_mutex.unlock();
			return true;
		}	
		else
		{
			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 执行,取出一个元素" << command << endl;
				//数据处理
			}

		
		}
		cout << "end" << endl;
	
	}
private:

	std::list<int>msgRecvQueue;
	mutex my_mutex;           //创建一个互斥量

};

int main()
{
 //   //一 创建线程和等待多个线程
	//vector<thread>mythreads;
	创建10个线程,线程入口函数统一使用 myprint
	1)多线程执行顺序是乱的
	2)这种join写法更容易写出稳定程序
	3)把thread对象放入到容器里,对管理大量线程有帮助
	//for (int i = 0; i < 10; i++)
	//{
	//	//创建10个线程,已经开始执行
	//	mythreads.push_back(thread(Myprint, i));
	//
	//}
	//for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	//{
	//	iter->join();
	//}
	//cout << "I LOVE CHINA" << endl;

	//二 数据共享
	//2.1只读数据
	//2.2 有读有写
	//最简单的不崩溃处理 读和写不能同时进行
	//2.3其他案例
	//数据共享:    
	
	//三 共享数据的保护案例代码
	//网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。
	//另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。
	//使用list,频繁的按顺序插入和删除时效率较高


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

	A myobja;
	thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsg.join();
	myInMsgObj.join();
	//步骤:先lock 操作共享数据 然后unlock
	//lock和unlock要成对使用。

}


就可以同时进行读写操作
在这里插入图片描述

2.2 std::lock_guard类模板

std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock
示例:

// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
using namespace std;


//vector<int>g_v = { 1,2,3 };
//
//void Myprint(int inum)
//{
//	cout << "myprint线程开始执行了,线程编号" << inum << endl;
//	cout << "myprint线程结束执行了,线程编号" << inum << endl;
//	cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;
//
//}



class A
{
public:
	//把玩家命令放入到一个队列的进程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			my_mutex.lock();
			cout << "inMsgRecvQueue执行,插入一个元素" << i << endl;
			
			msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列	
			my_mutex.unlock();
		}
	}


	bool outMsgLULproc(int &command)
	{
		lock_guard<mutex>sbguard(my_mutex);       //sbguard是对象名
		/*my_mutex.lock();*/
		if (!msgRecvQueue.empty())
		{
		    command = msgRecvQueue.front();      //返回第一个元素但不检查元素是否存在
			msgRecvQueue.pop_front();            //移除第一个元素但不返回
			//处理数据。。。。。
			/*my_mutex.unlock();*/
			return true;
		}	
		else
		{
			/*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 执行,取出一个元素" << command << endl;
				//数据处理
			}

		
		}
		cout << "end" << endl;
	
	}
private:

	std::list<int>msgRecvQueue;
	mutex my_mutex;           //创建一个互斥量

};

int main()
{
 //   //一 创建线程和等待多个线程
	//vector<thread>mythreads;
	创建10个线程,线程入口函数统一使用 myprint
	1)多线程执行顺序是乱的
	2)这种join写法更容易写出稳定程序
	3)把thread对象放入到容器里,对管理大量线程有帮助
	//for (int i = 0; i < 10; i++)
	//{
	//	//创建10个线程,已经开始执行
	//	mythreads.push_back(thread(Myprint, i));
	//
	//}
	//for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	//{
	//	iter->join();
	//}
	//cout << "I LOVE CHINA" << endl;

	//二 数据共享
	//2.1只读数据
	//2.2 有读有写
	//最简单的不崩溃处理 读和写不能同时进行
	//2.3其他案例
	//数据共享:    
	
	//三 共享数据的保护案例代码
	//网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。
	//另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。
	//使用list,频繁的按顺序插入和删除时效率较高


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

	A myobja;
	thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsg.join();
	myInMsgObj.join();


	//步骤:先lock 操作共享数据 然后unlock
	//lock和unlock要成对使用。有lock忘记unlock的问题非常难排查
	//为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板
	//智能指针(unique_ptr<>)

	//std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock

}

工作原理:lock_guard构造函数里执行了lock函数,析构lock_guard时,执行了unlock函数

3 死锁

3.1 死锁演示

死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)
两个线程A,B
线程A执行时,先锁金锁,然后准备去锁银锁,两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁产生了死锁,下面用两个互斥量演示死锁。
代码演示:

// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
using namespace std;


//vector<int>g_v = { 1,2,3 };
//
//void Myprint(int inum)
//{
//	cout << "myprint线程开始执行了,线程编号" << inum << endl;
//	cout << "myprint线程结束执行了,线程编号" << inum << endl;
//	cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;
//
//}



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


				my_mutex1.lock();		//先锁金锁 实际中两个lock间会执行其他的东西
				my_mutex2.lock();		//再锁银锁
				msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列	
				my_mutex2.unlock();				//顺序无所谓
				my_mutex1.unlock();

			}
		}
	}


	bool outMsgLULproc(int &command)
	{
		//lock_guard<mutex>sbguard(my_mutex1);       //sbguard是对象名
		my_mutex2.lock();
		my_mutex1.lock();
		if (!msgRecvQueue.empty())
		{
		    command = msgRecvQueue.front();      //返回第一个元素但不检查元素是否存在
			msgRecvQueue.pop_front();            //移除第一个元素但不返回
			//处理数据。。。。。
			my_mutex1.unlock();
			my_mutex2.unlock();
			return true;
		}	
		else
		{
			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 执行,取出一个元素" << command << endl;
				//数据处理
			}

		
		}
		cout << "end" << endl;
	
	}
private:

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

};

int main()
{
 //   //一 创建线程和等待多个线程
	//vector<thread>mythreads;
	创建10个线程,线程入口函数统一使用 myprint
	1)多线程执行顺序是乱的
	2)这种join写法更容易写出稳定程序
	3)把thread对象放入到容器里,对管理大量线程有帮助
	//for (int i = 0; i < 10; i++)
	//{
	//	//创建10个线程,已经开始执行
	//	mythreads.push_back(thread(Myprint, i));
	//
	//}
	//for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	//{
	//	iter->join();
	//}
	//cout << "I LOVE CHINA" << endl;

	//二 数据共享
	//2.1只读数据
	//2.2 有读有写
	//最简单的不崩溃处理 读和写不能同时进行
	//2.3其他案例
	//数据共享:    
	
	//三 共享数据的保护案例代码
	//网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。
	//另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。
	//使用list,频繁的按顺序插入和删除时效率较高


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

	A myobja;
	thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsg.join();
	myInMsgObj.join();

	//四 互斥量的概念
	//步骤:先lock 操作共享数据 然后unlock
	//lock和unlock要成对使用。有lock忘记unlock的问题非常难排查
	//为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板
	//智能指针(unique_ptr<>)

	//std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock
	//要将保护量放在lock和unlock里


	//五 死锁
	/*
	**死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)**
	两个线程A,B
	线程A执行时,先锁金锁,然后去锁银锁
	两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁
	此时产生了死锁
	线程A锁不了银锁,流程走不下去
	线程B锁不了金锁,流程走不下去
	*/

	//5.1死锁演示
}


在这里插入图片描述

3.2 死锁的解决方案

只要保证两个互斥量上锁的顺序一致,就不会造成死锁。
(解锁顺序不影响是否发生死锁)

// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
using namespace std;


//vector<int>g_v = { 1,2,3 };
//
//void Myprint(int inum)
//{
//	cout << "myprint线程开始执行了,线程编号" << inum << endl;
//	cout << "myprint线程结束执行了,线程编号" << inum << endl;
//	cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;
//
//}



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


				my_mutex1.lock();		//先锁金锁 实际中两个lock间会执行其他的东西
				my_mutex2.lock();		//再锁银锁
				msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列	
				my_mutex2.unlock();				//顺序无所谓
				my_mutex1.unlock();

			}
		}
	}


	bool outMsgLULproc(int &command)
	{
		//lock_guard<mutex>sbguard(my_mutex1);       //sbguard是对象名
		my_mutex1.lock();
		my_mutex2.lock();
		if (!msgRecvQueue.empty())
		{
		    command = msgRecvQueue.front();      //返回第一个元素但不检查元素是否存在
			msgRecvQueue.pop_front();            //移除第一个元素但不返回
			//处理数据。。。。。
			my_mutex1.unlock();
			my_mutex2.unlock();
			return true;
		}	
		else
		{
			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 执行,取出一个元素" << command << endl;
				//数据处理
			}

		
		}
		cout << "end" << endl;
	
	}
private:

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

};

int main()
{
 //   //一 创建线程和等待多个线程
	//vector<thread>mythreads;
	创建10个线程,线程入口函数统一使用 myprint
	1)多线程执行顺序是乱的
	2)这种join写法更容易写出稳定程序
	3)把thread对象放入到容器里,对管理大量线程有帮助
	//for (int i = 0; i < 10; i++)
	//{
	//	//创建10个线程,已经开始执行
	//	mythreads.push_back(thread(Myprint, i));
	//
	//}
	//for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	//{
	//	iter->join();
	//}
	//cout << "I LOVE CHINA" << endl;

	//二 数据共享
	//2.1只读数据
	//2.2 有读有写
	//最简单的不崩溃处理 读和写不能同时进行
	//2.3其他案例
	//数据共享:    
	
	//三 共享数据的保护案例代码
	//网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。
	//另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。
	//使用list,频繁的按顺序插入和删除时效率较高


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

	A myobja;
	thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsg.join();
	myInMsgObj.join();

	//四 互斥量的概念
	//步骤:先lock 操作共享数据 然后unlock
	//lock和unlock要成对使用。有lock忘记unlock的问题非常难排查
	//为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板
	//智能指针(unique_ptr<>)

	//std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock
	//要将保护量放在lock和unlock里


	//五 死锁
	/*
	**死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)**
	两个线程A,B
	线程A执行时,先锁金锁,然后去锁银锁
	两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁
	此时产生了死锁
	线程A锁不了银锁,流程走不下去
	线程B锁不了金锁,流程走不下去
	死锁产生的关键是两个互斥量的上锁顺序不一致
	*/

	//5.1死锁演示
	//5.2死锁的一般解决方案
	//只要保证两个互斥量上锁的顺序一致,就不会造成死锁。

}


在这里插入图片描述
使用lock_guard产生死锁和解决死锁的方法相同。

3.3 std::lock()函数模板

能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行),不存在因为锁头的顺序问题导致出现死锁问题。如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走
特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。
一般用来处理多个互斥量的情况

缺点:需要手动解锁
用一个std:lock()和两个unlock来保护共享数据,示例:

// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
using namespace std;


//vector<int>g_v = { 1,2,3 };
//
//void Myprint(int inum)
//{
//	cout << "myprint线程开始执行了,线程编号" << inum << endl;
//	cout << "myprint线程结束执行了,线程编号" << inum << endl;
//	cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;
//
//}



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


				//my_mutex1.lock();		//先锁金锁 实际中两个lock间会执行其他的东西
				//my_mutex2.lock();		//再锁银锁
				//使用std::lock()
				std::lock(my_mutex1, my_mutex2);    //相当与每个互斥量都调用了lock
				msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列	

				my_mutex2.unlock();				//顺序无所谓
				my_mutex1.unlock();

			}
		}
	}


	bool outMsgLULproc(int &command)
	{
		//lock_guard<mutex>sbguard(my_mutex1);       //sbguard是对象名
		//my_mutex1.lock();
		//my_mutex2.lock();
		std::lock(my_mutex1, my_mutex2);
		if (!msgRecvQueue.empty())
		{
		    command = msgRecvQueue.front();      //返回第一个元素但不检查元素是否存在
			msgRecvQueue.pop_front();            //移除第一个元素但不返回
			//处理数据。。。。。
			my_mutex1.unlock();
			my_mutex2.unlock();
			return true;
		}	
		else
		{
			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 执行,取出一个元素" << command << endl;
				//数据处理
			}

		
		}
		cout << "end" << endl;
	
	}
private:

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

};

int main()
{
 //   //一 创建线程和等待多个线程
	//vector<thread>mythreads;
	创建10个线程,线程入口函数统一使用 myprint
	1)多线程执行顺序是乱的
	2)这种join写法更容易写出稳定程序
	3)把thread对象放入到容器里,对管理大量线程有帮助
	//for (int i = 0; i < 10; i++)
	//{
	//	//创建10个线程,已经开始执行
	//	mythreads.push_back(thread(Myprint, i));
	//
	//}
	//for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	//{
	//	iter->join();
	//}
	//cout << "I LOVE CHINA" << endl;

	//二 数据共享
	//2.1只读数据
	//2.2 有读有写
	//最简单的不崩溃处理 读和写不能同时进行
	//2.3其他案例
	//数据共享:    
	
	//三 共享数据的保护案例代码
	//网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。
	//另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。
	//使用list,频繁的按顺序插入和删除时效率较高


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

	A myobja;
	thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsg.join();
	myInMsgObj.join();

	//四 互斥量的概念
	//步骤:先lock 操作共享数据 然后unlock
	//lock和unlock要成对使用。有lock忘记unlock的问题非常难排查
	//为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板
	//智能指针(unique_ptr<>)

	//std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock
	//要将保护量放在lock和unlock里


	//五 死锁
	/*
	**死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)**
	两个线程A,B
	线程A执行时,先锁金锁,然后去锁银锁
	两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁
	此时产生了死锁
	线程A锁不了银锁,流程走不下去
	线程B锁不了金锁,流程走不下去
	死锁产生的关键是两个互斥量的上锁顺序不一致
	*/

	//5.1死锁演示
	//5.2死锁的一般解决方案
	//只要保证两个互斥量上锁的顺序一致,就不会造成死锁。


	//5.3 std::lock()函数模板
	//能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行)
	//不存在因为锁头的顺序问题导致出现死锁问题。
	//如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走
	//特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。
	//用来处理多个互斥量的情况


}


程序正常运行
在这里插入图片描述
但这样使用还存在这需要手动写解代码的问题。

3.4 std:lock_guard的std::adopt_lock参数

为了解决使用了std::lock()还需要手动解锁的问题,使用不需要手写解锁代码的std::lock_guard,但是在std::lock(mutex1,mutex2)中,已经上锁一次,再使用std::lock_guard还会再上锁一次,所以使用std::lock_guard中的adopt_lock参数,可以在不调用上锁函数,保证只锁一次。
示例代码如下:

// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
using namespace std;


//vector<int>g_v = { 1,2,3 };
//
//void Myprint(int inum)
//{
//	cout << "myprint线程开始执行了,线程编号" << inum << endl;
//	cout << "myprint线程结束执行了,线程编号" << inum << endl;
//	cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;
//
//}



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


				//my_mutex1.lock();		//先锁金锁 实际中两个lock间会执行其他的东西
				//my_mutex2.lock();		//再锁银锁
				//使用std::lock()
				std::lock(my_mutex1, my_mutex2);    //相当与每个互斥量都调用了lock
				std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock);
				std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock);
				msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列	

				//my_mutex2.unlock();				//顺序无所谓
				//my_mutex1.unlock();

			}
		}
	}


	bool outMsgLULproc(int &command)
	{
		//lock_guard<mutex>sbguard(my_mutex1);       //sbguard是对象名
		//my_mutex1.lock();
		//my_mutex2.lock();
		std::lock(my_mutex1, my_mutex2);
		if (!msgRecvQueue.empty())
		{
		    command = msgRecvQueue.front();      //返回第一个元素但不检查元素是否存在
			msgRecvQueue.pop_front();            //移除第一个元素但不返回
			//处理数据。。。。。
			my_mutex1.unlock();
			my_mutex2.unlock();
			return true;
		}	
		else
		{
			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 执行,取出一个元素" << command << endl;
				//数据处理
			}

		
		}
		cout << "end" << endl;
	
	}
private:

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

};

int main()
{
 //   //一 创建线程和等待多个线程
	//vector<thread>mythreads;
	创建10个线程,线程入口函数统一使用 myprint
	1)多线程执行顺序是乱的
	2)这种join写法更容易写出稳定程序
	3)把thread对象放入到容器里,对管理大量线程有帮助
	//for (int i = 0; i < 10; i++)
	//{
	//	//创建10个线程,已经开始执行
	//	mythreads.push_back(thread(Myprint, i));
	//
	//}
	//for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	//{
	//	iter->join();
	//}
	//cout << "I LOVE CHINA" << endl;

	//二 数据共享
	//2.1只读数据
	//2.2 有读有写
	//最简单的不崩溃处理 读和写不能同时进行
	//2.3其他案例
	//数据共享:    
	
	//三 共享数据的保护案例代码
	//网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。
	//另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。
	//使用list,频繁的按顺序插入和删除时效率较高


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

	A myobja;
	thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutnMsg.join();
	myInMsgObj.join();

	//四 互斥量的概念
	//步骤:先lock 操作共享数据 然后unlock
	//lock和unlock要成对使用。有lock忘记unlock的问题非常难排查
	//为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板
	//智能指针(unique_ptr<>)

	//std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock
	//要将保护量放在lock和unlock里


	//五 死锁
	/*
	**死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)**
	两个线程A,B
	线程A执行时,先锁金锁,然后去锁银锁
	两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁
	此时产生了死锁
	线程A锁不了银锁,流程走不下去
	线程B锁不了金锁,流程走不下去
	死锁产生的关键是两个互斥量的上锁顺序不一致
	*/

	//5.1死锁演示
	//5.2死锁的一般解决方案
	//只要保证两个互斥量上锁的顺序一致,就不会造成死锁。


	//5.3 std::lock()函数模板
	//能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行)
	//不存在因为锁头的顺序问题导致出现死锁问题。
	//如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走
	//特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。
	//用来处理多个互斥量的情况

	//5.4 std:lock_guard的std:adopt_lock参数



}


在这里插入图片描述
通过std::lock(mutex1,mutex2,…)和std::lock_guard相结合的方式,可以做到上锁时不考虑上锁顺序,同时不需要手写解锁代码。
总结:在需要保护共享数据时,最安全的方法时一个一个上锁,一个一个解锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Michael.Scofield

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值