并发编程5——互斥量和死锁

目录

一、互斥量

上次说到了我们需要保护共享数据,如何保护?

  • 操作的时候,依靠线程A把共享数据锁住,操作数据,解锁
  • 其他想要操作共享数据的线程B或C,必须等待线程A解锁,然后B或C将共享数据锁定住,操作数据,解锁
  • 互斥量是一个类对象

理解成一把,多个线程尝试使用lock()成员函数来加锁,但是只有一个线程可以锁定成功。

二、互斥量的用法

2.1 lock()和unlock()

线程加锁成功的标志是——lock()函数成功返回,不然会一直卡在lock()那里。

  • lock()
  • unlock()

步骤:

    1. 先lock()
    1. 操作共享数据
    1. unlock()

成对使用,不然会报出异常

忘记unlock()报出异常

2.2 std::lock_guard类模板

如果忘记了unlock(),会有一个类模板lock_guard,可以直接取代lock()和unlock()

相当于智能指针——忘记释放内存没关系,智能指针可以帮你释放

  • 不过有了lock_guard()以后,就不能再放lock()和unlock(),不然也会异常
lock_guard不能与lock和unlock共存

为什么二者会冲突呢?

  • 因为lock_guard的构造函数里执行了mutex::lock()函数
  • 因为lock_guard的析构函数里执行了mutex::unlock()函数
lock_guard类

lock_guard完整类模板

三、死锁

3.1 死锁概念

  • 死锁是由至少两个锁,也就是2个互斥量,才能产生的

比如有金锁和银锁,线程A和B

  • 线程A执行的时候,线程A先锁了金锁,并成功了,then要去锁银锁
  • 出现了上下文切换
  • 线程B执行,线程B锁了银锁,并成功了,then要去锁金锁

这样就会产生死锁——

  • 因为线程A拿不到银锁头,所以在一直等待,从而金锁头也会一直被Alock()
  • 因为线程B拿不到金锁头,所以也一直等待,从而银锁头也会一直被Block()
  • 从而大家都在等对方释放资源才能执行

代码实现中,因为线程A先调用了mutex1的lock,线程B调用了mutex2的lock,这样就会死锁,解决方案就是二者都先调用mutex1的lock就行。

不过我的代码并没有死锁(停滞),不知道为啥...

但是使用lock_guard成功产生了死锁

死锁

可以看到,没执行完就停滞了

3.2 死锁的一般解决方案

  • 只要保证2个互斥量上锁的顺序一致,就不会死锁

3.3 std::lock()函数模板

这个是用于处理多个互斥量
它的能力——可以一次锁住2个或2个以上的互斥量(必须至少2个)

从而,它不存在,因为在多个线程中,因为锁的顺序导致的死锁问题

std::lock()的用法

这样的好处是不用在乎lock,但是如果忘记unlock()还是会出问题——

忘记unlock()

3.4 std::lock_guard的std::adopt_lock参数

  • std::adopt_lock是一个结构体对象,起一个标记作用

作用就是表示这个互斥量已经lock()过了,不需要在lock_guard的构造函数中再对mutex对象进行lock()了

总结

std::lock()是一次可以锁定多个互斥量的,还是建议一个一个锁


博客示例源代码

  • lock()和unlock()
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>

using namespace std;

class A
{
public:

    // 把收到的消息(玩家命令)放入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; ++ i )
        {
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

            my_mutex.lock();
            msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
                                       // 直接把它弄到消息队列中
            my_mutex.unlock();
        }

        return;
    }

    bool outMsgLULProc(int& command)
    {
        my_mutex.lock();
        if (msgRecvQueue.size())
        {
            // 消息不为空
            int command = msgRecvQueue.front(); // 返回第一个元素
            msgRecvQueue.pop_front();           // 移除第一个元素
            
            my_mutex.unlock();
            return true;
        }

        my_mutex.unlock();
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; ++ i )
        {
            bool res = outMsgLULProc(command);
            if (res) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;

            // 如果消息队列为空
            else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
        }

        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
};


int main()
{
    A myobj_a;
    
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);

    myInMsgObj.join();
    myOutMsgObj.join();

    return 0;
}
  • std::lock_guard()
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>

using namespace std;

class A
{
public:

    // 把收到的消息(玩家命令)放入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; ++ i )
        {
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

            my_mutex.lock();
            msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
                                       // 直接把它弄到消息队列中
            my_mutex.unlock();
        }

        return;
    }

    bool outMsgLULProc(int& command)
    {
        std::lock_guard<std::mutex> go_guard(my_mutex);

        // my_mutex.lock();
        if (msgRecvQueue.size())
        {
            // 消息不为空
            int command = msgRecvQueue.front(); // 返回第一个元素
            msgRecvQueue.pop_front();           // 移除第一个元素
            
            // my_mutex.unlock();
            return true;
        }

        // my_mutex.unlock();
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; ++ i )
        {
            bool res = outMsgLULProc(command);
            if (res) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;

            // 如果消息队列为空
            else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
        }

        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
};


int main()
{
    A myobj_a;
    
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);

    myInMsgObj.join();
    myOutMsgObj.join();

    return 0;
}
  • 死锁(lock和unlock)
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>

using namespace std;

class A
{
public:

    // 把收到的消息(玩家命令)放入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; i ++ )
        {
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

            my_mutex1.lock();
            my_mutex2.lock();
            msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
                                       // 直接把它弄到消息队列中
            my_mutex2.unlock();
            my_mutex1.unlock();
        }

        return;
    }

    bool outMsgLULProc(int& command)
    {
        // std::lock_guard<std::mutex> go_guard(my_mutex1);

        my_mutex2.lock();
        my_mutex1.lock();
        if (!msgRecvQueue.empty())
        {
            // 消息不为空
            int command = msgRecvQueue.front(); // 返回第一个元素
            msgRecvQueue.pop_front();           // 移除第一个元素
            
            my_mutex1.unlock();
            my_mutex2.unlock();

            return true;
        }

        my_mutex1.unlock();
        my_mutex2.unlock();

        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; i ++ )
        {
            bool res = outMsgLULProc(command);
            if (res == true) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;

            // 如果消息队列为空
            else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
        }

        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
    std::mutex my_mutex1;        // 创建一个互斥量
    std::mutex my_mutex2;
};


int main()
{
    A myobj_a;
    
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);

    myInMsgObj.join();
    myOutMsgObj.join();
}
  • 死锁(lock_guard)
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>

using namespace std;

class A
{
public:

    // 把收到的消息(玩家命令)放入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; i ++ )
        {
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

            /*my_mutex1.lock();
            my_mutex2.lock();*/

            std::lock_guard<std::mutex> go_guard1(my_mutex1);
            std::lock_guard<std::mutex> go_guard2(my_mutex2);

            msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
                                       // 直接把它弄到消息队列中
            /*my_mutex2.unlock();
            my_mutex1.unlock();*/
        }

        return;
    }

    bool outMsgLULProc(int& command)
    {
        std::lock_guard<std::mutex> go_guard2(my_mutex2);
        std::lock_guard<std::mutex> go_guard1(my_mutex1);

        /*my_mutex2.lock();
        my_mutex1.lock();*/
        if (!msgRecvQueue.empty())
        {
            // 消息不为空
            int command = msgRecvQueue.front(); // 返回第一个元素
            msgRecvQueue.pop_front();           // 移除第一个元素
            
            /*my_mutex1.unlock();
            my_mutex2.unlock();*/

            return true;
        }

        /*my_mutex1.unlock();
        my_mutex2.unlock();*/

        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; i ++ )
        {
            bool res = outMsgLULProc(command);
            if (res == true) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;

            // 如果消息队列为空
            else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
        }

        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
    std::mutex my_mutex1;        // 创建一个互斥量
    std::mutex my_mutex2;
};


int main()
{
    A myobj_a;
    
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);

    myInMsgObj.join();
    myOutMsgObj.join();
}
  • std::lock()
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>

using namespace std;

class A
{
public:

    // 把收到的消息(玩家命令)放入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; i ++ )
        {
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

            /*my_mutex1.lock();
            my_mutex2.lock();*/

            /*std::lock_guard<std::mutex> go_guard1(my_mutex1);
            std::lock_guard<std::mutex> go_guard2(my_mutex2);*/

            std::lock(my_mutex1, my_mutex2); // 相当于每个互斥量都调用了lock()

            msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
                                       // 直接把它弄到消息队列中
            my_mutex2.unlock();
            my_mutex1.unlock();
        }

        return;
    }

    bool outMsgLULProc(int& command)
    {
        /*std::lock_guard<std::mutex> go_guard2(my_mutex2);
        std::lock_guard<std::mutex> go_guard1(my_mutex1);*/

        /*my_mutex2.lock();
        my_mutex1.lock();*/

        std::lock(my_mutex1, my_mutex2);
        if (!msgRecvQueue.empty())
        {
            // 消息不为空
            int command = msgRecvQueue.front(); // 返回第一个元素
            msgRecvQueue.pop_front();           // 移除第一个元素
            
            my_mutex1.unlock();
            my_mutex2.unlock();

            return true;
        }

        my_mutex1.unlock();
        my_mutex2.unlock();

        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; i ++ )
        {
            bool res = outMsgLULProc(command);
            if (res == true) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;

            // 如果消息队列为空
            else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
        }

        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
    std::mutex my_mutex1;        // 创建一个互斥量
    std::mutex my_mutex2;
};


int main()
{
    A myobj_a;
    
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);

    myInMsgObj.join();
    myOutMsgObj.join();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358