• 五、互斥量


    互斥量:

      是个类对象,理解成一把锁,多个线程尝试用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() 谨慎使用(建议一个一个锁);

    心之所愿,永不相忘
  • 相关阅读:
    设计模式之观察者模式
    设计模式之代理模式
    用Javascript模拟微信飞机大战游戏
    [Leetcode] Remove Duplicates from Sorted List II
    [Leetcode] Remove Duplicates from Sorted List
    [Leetcode] Remove Duplicates from Sorted Array II
    [Leetcode] Palindrome Number
    [Leetcode] Decode Ways
    [Leetcode] Climbing Stairs
    [Leetcode] Maximum Subarray
  • 原文地址:https://www.cnblogs.com/zgll/p/15291381.html
Copyright © 2020-2023  润新知