C++11并发与多线程笔记(6) unique_lock(类模板)

C++11并发与多线程笔记(6) unique_lock(类模板)

1、unique_lock取代lock_guard

unique_lock 是一个类模板。

unique_lock 比 lock_guard 灵活很多 (多出来很多用法),效率差一点,内存占用多一些。
使用:
unique_lock<mutex> myUniLock(myMutex);

2、unique_lock的第二个参数

2.1 std::adopt_lock:

std::adopt_lock:标记作用,表示这个互斥量已经被lock()(方便记忆:已经被lock()收养了,不需要再次lock() ),即不需要在构造函数中lock这个互斥量 了。
前提 :必须提前lock,否则会出错

lock_guard中也可以用这个参数

2.2 std::try_to_lock:

  • 尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里
  • 使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock 这个地方
    前提 :不能提前lock();
    owns_lock()方法判断是否拿到锁,如拿到返回true
    实例:
    假设
    线程1 执行下例代码时,先锁定互斥量myMutex1
    ,然后暂停了2s,才继续执行程序。线程1执行的代码如下
cpp 复制代码
bool outMsgProc(int &num)
{
	unique_lock<mutex> muGuard(myMutex1);
	chrono::milliseconds time(2000);    //线程暂停2s
	this_thread::sleep_for(time);
	if (!myList.empty())
	{
		num = myList.front();
		myList.pop_front();
		return true;
	}
	return false;
}

在此期间,如果线程2也尝试锁定myMutex1,就只能阻塞,直到线程1释放锁后,才能继续执行。线程2执行的代码如下:

cpp 复制代码
void InMsg()
{
	for (int i = 0; i < 10000; i++)
	{
		cout << "插入元素: " << i << endl;
		unique_lock<mutex> muGuard(myMutex1);
		myList.push_back(i);			
	}
}

显然,这样的程序效率不高。线程2花费了太多的时间等待。

我们考虑使用try_to_lock,线程尝试获取锁,如果没有锁定成功,它不会阻塞在那里,可以去执行其他代码。改进后的代码如下:

cpp 复制代码
void InMsg()
{
	for (int i = 0; i < 10000; i++)
	{
		cout << "插入元素: " << i << endl;
		unique_lock<mutex> muGuard(myMutex1, try_to_lock);
		if (muGuard.owns_lock())// 判断是否拿到锁
		{
			myList.push_back(i);
		}
		else
		{
			//没有拿到锁时,执行的代码
		}
	}
}

2.3 std::defer_lock:

  • 如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex
  • 不给它加锁的目的是以后可以调用unique_lock的一些方法
  • 前提 :不能提前lock

3、unique_lock的成员函数(前三个与std::defer_lock联合使用)

3.1 lock():加锁。

cpp 复制代码
unique_lock<mutex> muGuard(myMutex, defer_lock);
muGuard.lock();//手动加锁

不用自己unlock();

3.2 unlock():解锁。

cpp 复制代码
unique_lock<mutex> muGuard(myMutex, defer_lock);
muGuard.lock();
//处理一些共享代码
muGuard.unlock();
//临时处理一些非共享代码
muGuard.lock();
//处理一些共享代码,处理完之后,自动解锁

3.3 try_lock():尝试给互斥量加锁

try_to_lock是unique_lock的第二个参数,try_lock()是unique_lock()的成员变量。

如果拿不到锁,返回false,否则返回true(然后上锁)。

cpp 复制代码
 unique_lock<mutex>muGuard(myMutex, defer_lock);
 //mutex1.lock();//自动解锁
 if (muGuard.try_lock() == true) {
     cout << "插入数据: " << num << endl;
     test_list.push_back(num);
 }
 else {
     cout << "in_list()执行,但没有拿到锁" << num << endl;
 }

3.4 release():

release():就是解除绑定,返回它所管理的mutex对象的指针,并释放所有权。

  1. unique_lock<mutex>muGuard(myMutex1);
    相当于把myMutex(mutex对象)和 muGuard绑定在了一起
  2. mutex* ptx =muGuard.release();
    也就是说 muGuard和mutex不在有联系,后续myMutex所有权由ptx接管,如果原来mutex对象处理加锁状态,就需要ptx在以后进行解锁了。
cpp 复制代码
for (int num = 0; num < 10000; num++) {
    unique_lock<mutex> muGuard(myMutex);
    mutex* ptx = muGuard.release();//解除myMutex1(mutex对象)和 muGuard绑定
    //操作事务
    test_list.push_back(num);
    ptx->unlock();//myMutex1所有权由ptx接管,由ptx进行解锁
}

lock的代码段越少,执行越快,整个程序的运行效率越高。

  • 锁住的代码少,叫做粒度细 ,执行效率
  • 锁住的代码多,叫做粒度粗 ,执行效率
    -只锁定共享的数据

4.unique_lock所有权的传递

unique_lock<mutex> muGuard1(myMutex);

把myMutex1和muGuard绑定在了一起,也就是muGuard拥有myMutex1的所有权

4.1 使用move转移

unique_lock<mutex> muGuard2(std::move(muGuard1));

之前muGuard1拥有myMutex的所有权,muGuard1可以把自己对myMutex的所有权转移,但是不能复制。现在muGuard2拥有myMutex的所有权。

4.2. 在函数中return一个临时变量,也可以实现转移

cpp 复制代码
unique_lock<mutex> aFunction()
{
    unique_lock<mutex> tmpguard(myMutex);
    //移动构造函数那里讲从函数返回一个局部的unique_lock对象是可以的
    //返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
    return tmpguard;
}
// 然后就可以在外层调用,在muGuard2具有对myMutex的所有权
std::unique_lock<std::mutex> muGuard2 = aFunction();
相关推荐
起名字真南12 分钟前
【OJ题解】C++实现字符串大数相乘:无BigInteger库的字符串乘积解决方案
开发语言·c++·leetcode
少年负剑去12 分钟前
第十五届蓝桥杯C/C++B组题解——数字接龙
c语言·c++·蓝桥杯
cleveryuoyuo13 分钟前
AVL树的旋转
c++
爬山算法17 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
神仙别闹36 分钟前
基于MFC实现的赛车游戏
c++·游戏·mfc
2401_8574396941 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66642 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
小c君tt43 分钟前
MFC中 error C2440错误分析及解决方法
c++·mfc
李老头探索44 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter