C++11多线程---互斥量与死锁

一、互斥量的概念

作用场景:当一个代码里面有一个共享数据时,如果多个线程要使用这个共享数据,程序就有可能崩溃。因为对于一个共享数据来说,不可能一个线程在写数据的同时,另一个线程来读数据。因此要保护共享数据,用代码把共享数据锁住,当一个线程对共享数据读或写时,其他线程就必须等待。

互斥量(mutex)的基本概念:互斥量是个类对象,可以理解成一把锁。多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,锁定成功的标志是lock()函数返回。如果没锁成功,那么线程中的流程就会卡在lock()这里去不断的尝试加锁。

注意:互斥量在使用的过程中要慎重,加锁的范围不能多也不能少,多了会影响效率,少了可能会导致程序崩溃。

二、互斥量的用法

先添加头文件

#include<mutex>

对于要操作共享数据的线程,先lock()加锁,操作共享数据,最后unlock()释放。

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

案例代码:案例内涵看上篇:C++11多线程数据共享_阿巴乾的博客-CSDN博客

//
// Created by yangjq on 22-7-7.
//
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>

using namespace std;

class A{
public:
    //把玩家命令收到一个队列的线程
    void inMsgRecvQueue(){
        for(int i = 0; i < 1000000000; ++i){
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
            std::lock_guard<std::mutex> sbguard(my_mutex);
            //my_mutex.lock();
            msgRecvQueue.push_back(i);//数字i就是玩家收到的命令
            //my_mutex.unlock();
        }
    }

    bool outMsgLULProc(int &command){
        std::lock_guard<std::mutex> sbguard(my_mutex);
        //my_mutex.lock();
        if(!msgRecvQueue.empty()) {
            //消息不为空
            int command = msgRecvQueue.front();//返回前面的元素
            msgRecvQueue.pop_front();
            //my_mutex.unlock();
            //处理数据...
            return true;
        }
        //my_mutex.unlock();//两个出口需要两个unlock();
        return false;

    }


    //数据从消息队列中取出的线程
    void outMsgRecvQueue(){
        int command = 0;
        for(int i = 0; i < 100000000; ++i){
            bool result = outMsgLULProc(command);
            if(result){
                cout << "outMsgRecvQueue()执行,删除一个元素!" << endl;
            }
            else{
                cout << "outMsgRecvQueue()执行,消息队列为空" << i << endl;
            }
        }
    }

private:
    //容器,专门用于存储玩家发送过来的命令
    std::list<int> msgRecvQueue;
    std::mutex my_mutex;
};

int main()
{
    //用成员函数作为线程函数的方法来写线程
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    myInMsgObj.join();
    myOutMsgObj.join();

    cout << "main end !" << endl;
    return 0;
}

上面这份代码里面lock(),unlock()与lock_guard的加锁用法都有,前者被注释掉了。由于前者的用法需要配对,不配对就会报错,所以出现了lock_guard的用法。lock_guard是一个类模板,可以直接取代lock(),unlock()。

以上面代码中的lock_guard用法为例,解释以下lock_guard。在上面代码中

std::lock_guard<std::mutex> sbguard(my_mutex);

构造了一个对象,sbguard是随便起得对象名,在构造这个对象的过程中,执行了这个类的构造函数,这个构造函数中就有lock(),在退出函数时,会执行析构函数,析构函数中执行了unlock()。

因此,lock_guard只有在函数退出的时候才能解锁,为了提高效率,可以在局部加上{ },来提前进行析构函数提高效率。

三、死锁

死锁是由至少两个锁头也就是两个互斥量才能产生。在C++中,假设现在一段代码有两把锁,锁A与锁B。并且现在有两个线程,线程1与线程2。

  • 线程1执行的时候,线程1先锁上了锁A,然后需要去锁上锁B
  • 同时,线程2执行了,线程1先锁上了锁B,然后需要去锁上锁A

这时,死锁就产生了。两个线程都在等待已经被对方锁上的锁,都不能解锁,也都不能拿到对方的锁。

死锁演示代码:

//
// Created by yangjq on 22-7-7.
//
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>

using namespace std;

class A{
public:
    //把玩家命令收到一个队列的线程
    void inMsgRecvQueue(){
        for(int i = 0; i < 1000000000; ++i){
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
            my_mutex1.lock();
            my_mutex2.lock();
            msgRecvQueue.push_back(i);//数字i就是玩家收到的命令
            my_mutex2.unlock();
            my_mutex1.unlock();
        }
    }

    bool outMsgLULProc(int &command){
        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();//两个出口需要两个unlock();
        return false;

    }


    //数据从消息队列中取出的线程
    void outMsgRecvQueue(){
        int command = 0;
        for(int i = 0; i < 100000000; ++i){
            bool result = outMsgLULProc(command);
            if(result){
                cout << "outMsgRecvQueue()执行,删除一个元素!" << endl;
            }
            else{
                cout << "outMsgRecvQueue()执行,消息队列为空" << i << endl;
            }
        }
    }

private:
    //容器,专门用于存储玩家发送过来的命令
    std::list<int> msgRecvQueue;
    std::mutex my_mutex1;
    std::mutex my_mutex2;

};

int main()
{
    //用成员函数作为线程函数的方法来写线程
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    myInMsgObj.join();
    myOutMsgObj.join();

    cout << "main end !" << endl;
    return 0;
}

以上的代码运行时会卡住,那么死锁的问题怎么解决呢?

其实两个锁的顺序一致就不会死锁,都先锁1再锁2就不会发生死锁。

除此之外,可以利用std::lock(),使用方式是

std::lock(my_mutex1,my_mutex2)

这样就能同时对两个锁进行上锁,要么两个锁同时上锁,要么两个锁同时不锁。加入只有一个锁上锁成功,它会自动释放已上锁的锁。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿巴乾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值