c++11并发与多线程【第六节】unique_lock详解

第六节 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

文章内容来源《C++并发与多线程视频课程》

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 lk
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信