曾格的github

六、unique_lock取代lock_guard

unique是个类模板,工作中,一般lock_guard(推荐使用);

lock_guard取代了mutex的 lock() 和 unlock();unique_lock又取代lock_guard;qaq

unique和guard都是RAII风格的机制来的机制在作用域块的持续时间内拥有一个互斥量

  • 创建lock_guard对象时,它将获取提供给他的互斥锁的所有权,当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量,
    • 但是不能途中解锁。
    • 创建即锁定
  • unique_lock允许锁定包装器,它允许延迟锁定,限时深度锁定,递归锁定以及与条件变量一起使用,具有lock_guard的所有功能,能够应对更复杂的锁定需要。其有如下特点:
    • 创建时可不锁定(指定第二个参数为std::defer_lock),而在需要时再锁定。
    • 可以随时加锁解锁
    • 作用域同lock_guard,析构时自动释放锁
    • 不可复制,可移动
    • 条件变量需要该类型作为参数。

unique_lock比lock_guard灵活很多,效率上差一些,内存占用多一些。

#include <thread>
#include <iostream>
#include <list>
#include <mutex>
using namespace std;
 
class A {
public:
    void inMsgRecvQueue() {
        for (int i = 0; i < 10000; ++i) {
            cout << "inMsgRecvQueue插入一个元素" << i << endl;
            std::lock(mymutex1, mymutex2);
            std::unique_lock<std::mutex> sbguard1(mymutex1, std::adopt_lock);
            //std::lock_guard<std::mutex> sbguard2(mymutex2, std::adopt_lock);
            msgRecvQueue.push_back(i);
            //mymutex2.unlock();
            //...其他操作
            //mymutex1.unlock();
        }
    }
 
    bool outMsgProc(int &command) {
        std::lock(mymutex1, mymutex2);
        std::unique_lock<std::mutex> sbguard1(mymutex1, std::adopt_lock);
        //std::lock_guard<std::mutex> sbguard2(mymutex2, std::adopt_lock);
        if (!msgRecvQueue.empty()) {
            command = msgRecvQueue.front();
            msgRecvQueue.pop_front();
            //mymutex1.unlock();
            //mymutex2.unlock();
            return true;
        }
        //mymutex1.unlock();
        //mymutex2.unlock();
        return false;
    }
 
    void outMsgRecvQueue() {
        int command = 0;
        for (int i = 0; i < 10000; ++i) {
            bool re = outMsgProc(command);
            if (re == true) {
                cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;
            }
            else {
                cout << "消息队列为空" << endl;
            }
        }
    }
 
private:
    std::list<int> msgRecvQueue;
    std::mutex mymutex1;
    std::mutex mymutex2;
};
 
int main() {
    A myobja;
    std::thread myoutobj(&A::outMsgRecvQueue, &myobja); //注意这里myobja用引用,才能保证线程里用的是同一个对象
    std::thread myinobj(&A::inMsgRecvQueue, &myobja);
    myinobj.join();
    myoutobj.join();
}

总结:能用lock_guard就用它,它不行就用unique_lock。

unique_lock可以带第二个参数

1、std::adopt_lock:表示互斥量已经被lock了(程序员必须把互斥量先lock,否则会报异常)

效果:假设线程调用方赢拥有了互斥的所有权,lock_graud与unique_lock都可以加这个参数,意义相同。

2、std::try_to_lock

我们会尝试用mutex中的lock()去锁定这个mutex,但如果没有锁定成功,也会立刻返回,并不会阻塞

#include <thread>
#include <iostream>
#include <list>
#include <mutex>
using namespace std;
 
class A {
public:
    void inMsgRecvQueue() {
        for (int i = 0; i < 10000; ++i) {
            cout << "inMsgRecvQueue插入一个元素" << i << endl;
            
            std::unique_lock<std::mutex> sbguard1(mymutex1, std::try_to_lock);
            if (sbguard1.owns_lock()) {  //判断是否拿到了锁
                msgRecvQueue.push_back(i);  //拿到了锁
            }
            else {
                cout << "没能拿到锁" << endl;
            }
        }
    }
 
    bool outMsgProc(int &command) {
        mymutex1.lock();
        std::unique_lock<std::mutex> sbguard1(mymutex1, std::adopt_lock);
        //std::lock_guard<std::mutex> sbguard2(mymutex2, std::adopt_lock);
 
        std::chrono::milliseconds dura(20000);
        std::this_thread::sleep_for(dura);
 
        if (!msgRecvQueue.empty()) {
            command = msgRecvQueue.front();
            msgRecvQueue.pop_front();
            return true;
        }
        return false;
    }private:
    std::list<int> msgRecvQueue;
    std::mutex mymutex1;
};
 

通过这种方式让线程在没有拿到锁时执行其他事情

3、std::defer_lock:前提是你不能先lock(),否则汇报异常

  defer_lock的意思是并没有给mutex枷锁:初始化了一个没有加锁的mutex

unique的成员函数(与defer_lock配合使用)

1、lock():加锁,加锁后,不用必须加unlock,unique_lock会帮助我们unlock()

2、unlock():解锁,如果想自己解锁,也可以使用unlock()

为什么需要unlock(),因为你锁住的代码段越少,执行越快,整个程序效率就越高。

也有人把搜头锁住的代码多少称为 粒度。一般用粗细描述。

a、锁住的代码少,粒度细;b、锁住的代码多粒度粗

3、try_lock():尝试给互斥量加锁,如果拿不到锁返回false,如果拿到了锁,返回true

4、release():返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系

如果原来mutex对象处于加锁状态

std::mutex mymutex1;
 
std::unique_lock<std::mutex> myuniquemutex(mymutex1);
 
 
std::mutex *pmutex = myuniquemutex.release();
//此时,你就需要自己unlock()了

如果myuniquemutex还有锁没被释放,那pmutex需要负责释放。

unique_lock所有权传递

std::mutex mymutex1;
std::unique_lock<std::mutex> sbguard1(mymutex1);

此时,sbguard1拥有mymutex1的所有权,

std::unique_lock<std::mutex> sbguard2(sbguard1); //非法
std::unique_lock<std::mutex> sbguard2(std::move(sbguard1)); //移动语义,现在相当于sbguard2与mymutex1绑定到一起了
class Test{
public:
    std::mutex mymutex1;
    std::uniuqe_lock<std::mutex> rtn_guard(){
    
        std::unique_lock<std::mutex> tmpmutex(mymutex1);
        return tmpmutex; //因为tmpmutex为局部变量,函数返回局部变量时,会产生一个临 
                         //时对象,并调用移动构造函数传递给外部变量
    }
    std::uniuqe_lock<std::mutex> sbguard1 = rtn_guard();
}

 

posted @ 2021-09-15 23:43  曾格  阅读(211)  评论(0编辑  收藏  举报
Live2D