曾格的github

五、互斥量

互斥量:

  是个类对象,理解成一把锁,多个线程尝试用lock()来加锁这把锁头时,只有一个线程能锁定成功。(成功的标志是lock()函数返回),如果没锁成功,那么流程卡在lock()这里不断尝试去锁这把锁头(也就是每被调用寄存器一次,都会去申请锁)。

  慎用lock()保护共享数据不多也不少,少了达不到效果,多了影响效率;

lock()与unlock()要成对使用

  每调用一次lock()必然要调用一次unlock(),不能调用次数不一致,否则会导致代码不稳定甚至崩溃。

#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;
            mymutex.lock();
            msgRecvQueue.push_back(i);
            mymutex.unlock();
        }
    }
 
    bool outMsgProc(int &command) {
        mymutex.lock();
        if (!msgRecvQueue.empty()) {
            int command = msgRecvQueue.front();
            msgRecvQueue.pop_front();
            mymutex.unlock();  //两个代码分支最后都要解锁,否则会出现未解锁情况
            return true;
        }
        mymutex.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 mymutex;
};
 
 
 
int main() {
    A myobja;
    std::thread myoutobj(&A::outMsgRecvQueue, &myobja); 
    std::thread myinobj(&A::inMsgRecvQueue, &myobja);
    myinobj.join();
    myoutobj.join();
}

std::lock_guard的类模板

你忘记了unlock,系统替你unlock();

std::lock_guard类模板:直接取代lock()和unlock():也就是说用了lock_guard之后,再不能用lock()和unlock()了;

#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;
            //mymutex.lock();
                        {
                            std::lock_guard<std::mutex> sbguard(mymutex); //sbguard名称随意
                msgRecvQueue.push_back(i);
                        }
            //mymutex.unlock();
        }
    }
 
    bool outMsgProc(int &command) {
        std::lock_guard<std::mutex> sbguard(mymutex); //sbguard名称随意
        //lock_guard构造函数里执行了mutex::lock()
        //lock_guard析构函数里执行了mutex::unlock()
 
        //mymutex.lock();
        if (!msgRecvQueue.empty()) {
            int command = msgRecvQueue.front();
            msgRecvQueue.pop_front();
            //mymutex.unlock();
            return true;
        }
        //mymutex.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 mymutex;
};
 
int main() {
    A myobja;
    std::thread myoutobj(&A::outMsgRecvQueue, &myobja); 
    std::thread myinobj(&A::inMsgRecvQueue, &myobja);
    myinobj.join();
    myoutobj.join();
}

lock_guard相对还是没有lock()/unlock()灵活。

死锁

两个线程A,B,两把锁sa,sb

1、线程A执行的时候,这个线程先锁sa锁,把sa锁 lock()成功了,然后它去lock sb锁,还未成功时,出现了上下文切换。

2、线程B开始执行,这个线程先锁 sb锁,因为 sb锁 还没有被锁,所以 sb锁 会lock()成功,线程B要去锁 sa锁

3、此时死锁就产生了,线程A因为拿不到 sb锁 流程就走不下去(sa锁 就解不开)

4、线程B因为拿不到 sa锁 流程也走不下去(sb锁 就解不开)

死锁演示:

#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;
            mymutex1.lock();
            //...其他操作
            mymutex2.lock();
            msgRecvQueue.push_back(i);
            mymutex2.unlock();
            //...其他操作
            mymutex1.unlock();
        }
    }
 
    bool outMsgProc(int &command) {
        mymutex2.lock();
        mymutex1.lock();
        if (!msgRecvQueue.empty()) {
            int 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();
}

运行结果:卡住不动了。

死锁的一般解决方法:只要保证两个互斥量的调用顺序保持一致。

std::lock()模板

上面的lock时是std::mutex.lock()。

能力:一次锁住两个或以上互斥量(至少两个,多了不限),如果互斥量中有一个没锁住,它就在那里等着,直到所有互斥量都锁住,它才继续执行。

它不存在,因为在多个线程中,因为锁头的lock的顺序问题导致的死锁风险问题(若没锁住全部,它会将锁住的锁释放掉);

因此:要么两个互斥量都锁住,要么都没锁住。

std::lock_guard的std::adopt_lock参数

#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::lock_guard<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::lock_guard<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();
}

std::adopt_lock是一个结构体对象,起标记作用,就是表示这个互斥量已经lock(),不需要在std::lock_guard<std::mutex>里面对对象再次进行lock了

总结:

std::lock():一次性锁定多个互斥量,配合

std::lock_guard<std::mutex> sbguard1(mymutex1, std::adopt_lock);

实现不用在意加锁顺序与释放锁,但也可以手动unlock以实现细粒度锁。

std::lock() 谨慎使用(建议一个一个锁);

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