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

1.unique_lock取代lock_guard

unique_lock是一个类模板,使用方法上与lock_guard完全一致。
工作中一般用lock_guard。lock_guard取代了mutex的lock()和unlock()。unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。
用法示例:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 1000; ++i) 
		{
			std::unique_lock<std::mutex> sbguard1(mymutex);
			cout << "\ninMsgRecvQueue()执行,插入元素为" << i  << endl;
			msgRecvQueue.push_back(i);
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);
		if (!msgRecvQueue.empty()) 
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue() 
	{
		for (int i = 0; i < 1000; ++i) 
		{
			int result = outLULProc();
			if (result != -1) 
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result <<",这是第"<<i<<"次取操作"<<",此时队列中还剩下"<< msgRecvQueue.size()<<"个元素"<< endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	
	myInMsgObj.join();
	myOutMsgObj.join();

	return 0;
}

2.unique_lock的第二个参数

2.1 std::adopt_lock

表示互斥量已经被lock了,在前面必须有互斥量已经被lock的代码,否则会报异常。
用adopt_lock的前提是,这个线程之前已经被自己lock住了互斥量
std::adopt_lock标记的效果就是“假设调用方线程已经拥有了互斥的所有权(已经lock成功了)”,通知lock_guard不需要在构造函数中lock这个互斥量了。unique_lock也可以带这个标记,含义相同。就是不需要在unique_lock构造函数中再次lock这个互斥量。

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 1000; ++i) 
		{
			mymutex.lock();
			std::unique_lock<std::mutex> sbguard1(mymutex,std::adopt_lock);
			
			cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
			msgRecvQueue.push_back(i);
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		//std::chrono::milliseconds dura(2000);//2000毫秒=2秒
		//std::this_thread::sleep_for(dura);//休息一定时长

		if (!msgRecvQueue.empty()) 
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue() 
	{
		for (int i = 0; i < 1000; ++i) 
		{
			int result = outLULProc();
			if (result != -1) 
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result <<",这是第"<<i<<"次取操作"<<",此时队列中还剩下"<< msgRecvQueue.size()<<"个元素"<< endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
	
	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

2.1 std::try_to_lock

尝试用mutex的lock方法去锁定这个mutex,如果没有锁定成功,线程也会立即返回,并不会阻塞直到互斥量被释放。
用try_to_lock的前提是,这个线程之前没有自己lock住互斥量
就好像有人在用公厕,你试着去开门,门没打开你不会一直等在那里,而是去干别的事了

我们改造下程序,让其先执行outMsgRecvQueue线程,在执行inMsgRecvQueue线程。

thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

在每一次执行outMsgRecvQueue线程后,都会令其休息一段时间(2000ms=2s)。

std::chrono::milliseconds dura(20);//2000毫秒=2秒
std::this_thread::sleep_for(dura);//休息一定时长

由于outMsgRecvQueue执行的时候先上锁,再等待2s,再读取并删除数据,再释放锁。这时候inMsgRecvQueue才能给互斥量上锁。在进行写数据。所以每次inMsgRecvQueue都会等待outMsgRecvQueue至少2s执行完后获得锁后才执行。所以inMsgRecvQueue会被outMsgRecvQueue阻塞。因此让inMsgRecvQueue判断互斥量状态,如果可以上锁,那么就锁住写数据;不能上锁,就干别的事。

std::unique_lock<std::mutex> sbguard1(mymutex, std::try_to_lock);//尝试去锁住互斥量,没锁住的话会立即返回
if (sbguard1.owns_lock())
{
	//拿到了锁,执行这段
	cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
	msgRecvQueue.push_back(i);
}
else
{
	//没拿到锁,执行这段
	cout << "\ninMsgRecvQueue()执行,但是没有拿到锁" << i << endl;
}

但是实际情况inMsgRecvQueue很难拿到锁,因为outMsgRecvQueue休息时间太长了。我们将outMsgRecvQueue休息时间缩短到20ms,并增加了inMsgRecvQueue的尝试次数。inMsgRecvQueue在第2590次的时候抢到了锁插入成功。

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::unique_lock<std::mutex> sbguard1(mymutex, std::try_to_lock);//尝试去锁住互斥量,没锁住的话会立即返回
			if (sbguard1.owns_lock())
			{
				//拿到了锁,执行这段
				cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
				msgRecvQueue.push_back(i);
			}
			else
			{
				//没拿到锁,执行这段
				cout << "\ninMsgRecvQueue()执行,但是没有拿到锁" << i << endl;
			}
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		std::chrono::milliseconds dura(20);
		std::this_thread::sleep_for(dura);//休息一定时长

		if (!msgRecvQueue.empty())
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			int result = outLULProc();
			if (result != -1)
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

在这里插入图片描述

2.1 std::defer_lock

初始化一个没有加锁的mutex,以便使用unique_lock的成员函数
用defer_lock的前提是,这个线程之前没有自己lock住互斥量

3.unique_lock的成员函数

3.1 lock()

unique_lock也有自己的成员函数:

std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
sbguard1.lock();//不用自己unlock
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);

完整代码

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
			sbguard1.lock();//不用自己unlock
			cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
			msgRecvQueue.push_back(i);
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		if (!msgRecvQueue.empty())
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			int result = outLULProc();
			if (result != -1)
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

3.2 unlock()

因为可能有一些非共享代码要处理,可能需要提前解锁,所以unique_lock也支持自定义位置解锁。这样就不用等到unique_lock析构的时候自动解锁了。

std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
sbguard1.lock();
sbguard1.unlock();
//。。。。处理非共享数据
sbguard1.lock();//处理完非共享数据后再解锁
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
//析构时自动解锁

完整代码:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
			sbguard1.lock();
			sbguard1.unlock();
			//。。。。处理非共享数据
			sbguard1.lock();//处理完非共享数据后再解锁
			cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
			msgRecvQueue.push_back(i);
			//析构时自动解锁
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		if (!msgRecvQueue.empty())
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			int result = outLULProc();
			if (result != -1)
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

3.3 try_lock()

尝试给互斥量加锁,如果拿不到锁,则false,反之为true。这个不阻塞。

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
			if (sbguard1.try_lock())
			{
				//拿到了锁,执行这段
				cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
				msgRecvQueue.push_back(i);
			}
			else
			{
				//没拿到锁,执行这段
				cout << "\ninMsgRecvQueue()执行,但是没有拿到锁" << i << endl;
			}
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		std::chrono::milliseconds dura(20);
		std::this_thread::sleep_for(dura);//休息一定时长

		if (!msgRecvQueue.empty())
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			int result = outLULProc();
			if (result != -1)
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

inMsgRecvQueue在第5680次的时候抢到了锁插入成功
在这里插入图片描述

3.4 release()

返回它所管理的mutex对象指针并释放所有权。也就是说,unique_lock和mutex不再有关系。如果原来mutex处于加锁状态,需要自己接管过来并解锁

std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1,并给mymutex加锁
std::mutex *ptx = sbguard1.release();//解除unique_lock与mymutex关系,但此时mymutex仍然处于加锁状态

cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
ptx->unlock();//处理完共享数据后需要接管过来并解锁

完整代码:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1,并给mymutex加锁
			std::mutex *ptx = sbguard1.release();//解除unique_lock与mymutex关系,但此时mymutex仍然处于加锁状态

			cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
			msgRecvQueue.push_back(i);
			ptx->unlock();//处理完共享数据后需要接管过来并解锁
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		if (!msgRecvQueue.empty())
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			int result = outLULProc();
			if (result != -1)
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

lock的代码越少,执行越快,程序运行效率越高。锁住的代码叫做锁的粒度,一般用粗细表示。锁的代码少叫做细粒度,多就是粗粒度。选择合适粒度的代码进行保护。

4.unique_lock所有权的传递

所有权:sbguard1拥有mymutex的所有权,sbguard1可以把自己对mymutex的所有权转移给其他的unique_lock对象。所有权可以转移但是不可复制

所有权转移:方法1 移动语义

std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1
std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));//移动语义,现在相当于sbguard2和mymutex绑定到一起了

所有权转移:方法2 函数返回

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

std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();

完整代码:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>

using namespace std;

//用成员函数作为线程函数
class GameSever {
public:
	//所有权转移:方法2
	std::unique_lock<std::mutex> rtn_unique_lock()
	{
		std::unique_lock<std::mutex> tmpguard(mymutex);
		return tmpguard;//从函数返回局部的unique_lock对象是可以的。tmpguard会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
	}
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			//所有权转移:方法1
			//std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1
			//std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));//移动语义,现在相当于sbguard2和mymutex绑定到一起了
			std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();

			cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
			msgRecvQueue.push_back(i);
		}
		return;
	}


	int outLULProc()
	{
		std::unique_lock<std::mutex> sbguard1(mymutex);

		if (!msgRecvQueue.empty())
		{
			int cmd = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			return cmd;
		}
		return -1;
	}

	void outMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			int result = outLULProc();
			if (result != -1)
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
				//....这里考虑对读取的数据result进行处理
				mymutex.unlock();
			}
			else//消息队列为空
			{
				mymutex.lock();
				cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
				mymutex.unlock();
			}
			//cout << "\noutMsgRecvQueue执行完毕" << endl;
		}
	}
private:
	list<int> msgRecvQueue;
	std::mutex mymutex;
};

int main()
{
	GameSever gs;
	thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
	thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));

	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}```

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值