C++11并发与多线程笔记(6)unique_lock详解

文章目录

1、unique_lock取代lock_guard

unique_lock

  • 是一个类模板,工作中,一般用lock_guard(推荐使用);lock_guard取代了mutexlock()unlock()
  • unique_locklock_guard灵活很多;效率上差一点,内存占用多一点。
  • 常规使用,参数只有一个互斥量时,没有什么区别。

2、unique_lock第二个参数

2.1 std::adopt_lock

std::adopt_lock

  • std::adopt_lock:表示这个互斥量已经被lock了(使用前必须要把互斥量提前lock了,否则会报异常)。
  • std::adopt_lock标记的效果就是“假设调用方线程已经拥有了互斥的所有权(就是已经lock()成功了)。
  • 通知unique_lock不需要在构造函数中lock()这个互斥量了。

示例代码:

#include <iostream>
#include <thread>
#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();		//要先lock,后续才能用unique_lock的std::adopt_lock参数
			std::unique_lock<std::mutex> sbguard1(my_mutex,std::adopt_lock);

			msgRecvQueue.push_back(i);

		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

2.2 std::try_to_lock

std::try_to_lock()

  • 尝试用mutexlock()去锁定这个mutex。但如果没有锁定成功,也会立即返回,并不会阻塞到那里。
  • 用这个try_to_lock的前提是你自己不能先去lock

示例代码:

#include <iostream>
#include <thread>
#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::unique_lock<std::mutex> sbguard1(my_mutex,std::try_to_lock);
			if (sbguard1.owns_lock())
			{
				//拿到了锁
				msgRecvQueue.push_back(i);
				//......
			}
			else
			{
				cout << "inMsgRecvQueue()执行,但没有拿到锁,只能干点别的事" << i << endl;
			}
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		std::chrono::milliseconds dura(20000);		//1s = 1000ms,20000ms = 20s
		std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

2.3 std::defer_lock

std::defer_lock

  • std::defer_lock的前提是,你不嗯能够自己先lock(),否则会报异常。
  • defer_lock的意思就是:并没有给mutex加锁,初始化了一个没有加锁的mutex

3、unique_lock的成员函数

3.1 lock(),unlock()

示例代码:

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

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex, std::defer_lock);	//没有加锁的my_mutex
			sbguard1.lock();		//不用自己unlock

			//因为有一些非共享的代码要处理,所以要unlock()
			sbguard1.unlock();
			//这里处理一些非共享代码

			//处理完非共享代码后,继续上锁
			sbguard1.lock();
			//这里处理共享代码

			//拿到了锁
			msgRecvQueue.push_back(i);
			//......

			sbguard1.unlock();		//画蛇添足,但也可以
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		//std::chrono::milliseconds dura(20000);		//1s = 1000ms,20000ms = 20s
		//std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

3.2 try_lock())

try_lock()

  • 尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数不阻塞。

示例代码:

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

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex, std::defer_lock);	//没有加锁的my_mutex

			if (sbguard1.try_lock() == true)		//返回true表示拿到锁了
			{
				msgRecvQueue.push_back(i);
				cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
				//......
			}
			else
			{
				cout << "inMsgRecvQueue()执行,但没有拿到锁,只能干点别的事" << i << endl;
			}
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

3.3 release()

release()

  • 返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lockmutex不再有关系。
  • 严格区分unlock()release()的区别,不要混淆。
  • 如果原来的mutex对象处于加锁状态,程序员有责任接管过来并负责解锁。

示例代码:

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

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex);	//没有加锁的my_mutex
			std::mutex* ptx = sbguard1.release();	//现在你有责任自己解锁这个my_mutex

			msgRecvQueue.push_back(i);
			cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;

			ptx->unlock();		//自己负责mutex的unlock()
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

为什么有时候需要unlock()

  • 因为lock()锁住的代码段越少,执行越快,整个程序运行效率越高。
  • 锁头锁住的代码的多少称为锁的粒度,粒度一般用粗细来描述。
  • 锁住的代码少,这个粒度叫细,执行效率高。
  • 锁住的代码多,粒度叫粗,执行效率就低。
  • 要学会尽量选择合适粒度的代码进行保护,力度太细,可能漏掉共享数据的保护,粒度太粗,影响效率。
  • 选择合适的粒度,是高级程序员的能力和实力的体现。

4、unique_lock所有权的传递

  • std::unique_lock<std::mutex> sbguard1(my_mutex)
  • sbguard1拥有my_mutex的所有权
  • sbguard1可以把自己对mutex(my_mutex)的所有权转移给其他的unique_lock对象。
  • unique_lock对象对mutex的所有权可以转移,但不能复制。

4.1 std::move

第一种std::move所有权传递示例代码:

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

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex);	//没有加锁的my_mutex

			std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));

			msgRecvQueue.push_back(i);
			cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		//std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		//std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

4.2 return std::unique_lockstd::mutex

第二种return std::unique_lock<std::mutex>所有权传递示例代码:

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

class A
{
public:
	std::unique_lock<std::mutex> rtn_unique_lock()
	{
		std::unique_lock<std::mutex> tmpguard(my_mutex);
		return tmpguard;		//从函数返回一个局部的unique_lock对象是可以的
										//返回这种局部对象tmp_guard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
	}

	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> tmpguard1 = rtn_unique_lock();

			msgRecvQueue.push_back(i);
			cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		//std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		//std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			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()执行,取出一个元素" << command << endl;
				//接下来就考虑处理数据......
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//容器,专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex;					//创建了一个互斥量

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

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


	cout << "I live China!" << endl;	//最后执行这句,整个进程退出

	system("pause");
	return 0;
}

注:本人学习c++多线程视频地址:C++多线程学习地址

上一篇:我踩过的Django的坑


下一篇:2.37