第六节 unique_lock详解
一、unique_lock取代lock_guard
unique_lock是个类模板,一般情况工作中使用lock_guard就足够了;
lock_guard取代了mutex的lock()和unlock();
unique_lock相比lock_guard更灵活,效率上差一点,内存占有多一点;
使用方法:
std::unique_lock<std::mutex> lockguard(my_mutex1);
二、unique_lock的第二个参数
2.1 std::adopt_lock
与lock_guard的第二个参数类似,表示这个互斥量已经被lock了,(当然在使用这个参数之前,必须要把互斥量lock,否则会报错);
std::adopt_lock标记得效果就是“假设调用方线程已经拥有了这个互斥量的所有权,因为已经lock()成功了”;
然后通知unique_lock或者lock_guard不需要在构造函数中lock这个互斥量了;
my_mutex1.lock();//先lock之后才能用std::adopt_lock
std::unique_lock<std::mutex> lockguard(my_mutex1,std::adopt_lock);
2.2 std::try_to_lock
体现unique_lock灵活性案例:
线程一中:
std::unique_lock<std::mutex> lockguard(my_mutex1);
//做个延时,lock成功之后休息20秒
std::chrono::milliseconds dura(20000);//单位毫秒
std::this_thread::sleep_for(dura);//休息20秒
线程二中:
my_mutex1.lock();//先lock之后才能用std::adopt_lock
std::unique_lock<std::mutex> lockguard(my_mutex1,std::adopt_lock);
则当线程一lock成功之后,线程二由于无法lock会阻塞在这,直到线程一unlock,此时线程二由于阻塞无法执行任何操作;
使用std::try_to_lock之后,尝试使用mutex的lock()去锁定这互斥量mutex,但是如果没有锁定成功,就会立即返回,并不会一直阻塞在这个地方;
使用这个std::try_to_lock的前提是互斥量不能被自己先lock()了;
//
std::unique_lock<std::mutex> lockguard(my_mutex1,std::try_to_lock);//
//判断是否lock()成功
if(lockguard.owns_lock()){
msgRecvQueue.push_back(i);//把命令放入队列当中
//其他处理代码
cout<<"inMsgRecvQueue,lock成功了"<<endl;
}
else{
//没拿到锁
cout<<"inMsgRecvQueue,没有lock成功 :"<<i<<endl;
}
从上面代码可以看出,即使没有lock成功,整个线程也不会一直阻塞,而是可以去干别的事情;
2.3 std::defer_lock
使用std::defer_lock使用前提也是不能自己先lock();
defer_lock的意义是:初始化一个没有加锁的mutex,也就是说这个unique_lock对象不会在构造函数中自动给mutex加锁;
三. unique_lock的成员函数
3.1 lock()成员函数和unlock()成员函数,搭配defer_lock使用
std::unique_lock<std::mutex> lockguard(my_mutex1,std::defer_lock);//没有lock的my_mutex1
lockguard.lock();//调用unique_lock的对象来lock(),不使用mutex的lock
使用情况:
//把收到的消息(玩家命令)放入到一个队列的线程入口函数
void inMsgRecvQueue(){
for(int i=0;i<10000;i++){//用数字模拟玩家发送来的命令
cout<<"inMsgRecvQueue()执行,插入一个元素 "<<i<<endl;
//
std::unique_lock<std::mutex> lockguard(my_mutex1,std::defer_lock);//没有lock的my_mutex1
lockguard.lock();//调用unique_lock的对象来lock(),不使用mutex的lock
//处理一些共享代码
lockguard.unlock();//相当于临时性解锁去执行一些非共享代码
//处理非共享代码
lockguard.lock();
msgRecvQueue.push_back(i);//把命令放入队列当中
//其他处理代码
cout<<"inMsgRecvQueue,lock成功了"<<endl;
lockguard.unlock();///倘若不使用也可以,unique_lock的析构函数会作用域结束后会自动解锁
}
}
3.2 try_lock()成员函数,搭配defer_lock使用
尝试给互斥量加锁,如果不能锁,返回false,如果锁住了,返回true,这个函数不阻塞;
//把收到的消息(玩家命令)放入到一个队列的线程入口函数
void inMsgRecvQueue(){
for(int i=0;i<10000;i++){//用数字模拟玩家发送来的命令
//
std::unique_lock<std::mutex> lockguard(my_mutex1,std::defer_lock);//没有lock的my_mutex1
if(lockguard.try_lock()==true)//返回true表示拿到锁了
{
cout<<"inMsgRecvQueue()执行,插入一个元素 "<<i<<endl;
msgRecvQueue.push_back(i);//把命令放入队列当中
//其他处理代码
cout<<"inMsgRecvQueue,lock成功了"<<endl;
}
else{
cout<<"没有锁成功 "<<i<<endl;
}
}
}
3.3 release() 成员函数
返回它所管理的mutex对象指针,并释放所有权;也就是说unique_lock()和mutex不再有关系;
注意区分release和unlock;
下面这种写法相当于把mutex的对象my_mutex1与unique_lock绑定在一起,然后使用unique_lock来管理mutex对象;
std::unique_lock<std::mutex> lockguard(my_mutex1,std::defer_lock);
release()放弃了unique_lock与mutex对象的关系;
如果,mutex对象已经被lock了,release()之后要用户要负责对mutex进行unlock();
std::unique_lock<std::mutex> lockguard(my_mutex1);//构造函数中lock()
std::mutex *ptx=lockguard.release();//现在需要自己解锁,由用户接管mutex
//****共享数据操作
ptx->unlock();
release()返回的是std::mutex的指针;
结论:
为什么有时候需要成员函数unlock()?因为lock主的代码越少,执行越快,整个程序的运行效率越高。
也有人把锁头锁住的代码的多少称为锁的粒度,粒度一般用粗细来形容;
1.锁住的代码多,粒度粗,执行效率低;
2锁住的代码少,粒度细,执行效率高;
要选择合适粒度的代码进行保护,粒度太细,容易漏掉共享数据的保护,太粗,效率低;
四. unique_lock所有权的传递
一般情况下,unique_lock需要对一个mutex对象进行管理;一个mutex对象的所有权只能被一个unique_lock所拥有;
std::unique_lock<std::mutex> test(my_mutex1);
此时test拥有my_mutex1的所有权;
test可以把自己对my_mutex1的的所有权转移给其他的unique_lock对象;
unique_lock对象这个mutex的所有权可以使用std::move(unique_lock对象)来进行转移;
std::unique_lock<std::mutex> lockguard(my_mutex1);//构造函数中lock()
std::unique_lock<std::mutex> lockguard2(std::move(lockguard));//移动语义,相当于lockguard2与my_mutex1绑定在一起,拥有了my_mutex1的所有权,此时lockguard指向空
使用unique_lock作为函数返回值传递mutex对象的所有权:
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tempguard(my_mutex1);
return tempguard;//返回一个局部的unique_lock对象,返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
}
std::unique_lock<std::mutex> sguard=rtn_unique_lock();//此时sguard可以接受到my_mutex1的所有权;
所有权传递的方法:
1.std::move()
2.函数return