C++11并发与多线程【第六节】

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值