并发与多线程五:互斥量概念、用法、死锁演示及解决详解

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>

using namespace std;
class A
{
public://把受到的消息(玩家命令)入到一个队列的线程
    void inmsgrectqueue()
    {
        for (int i = 0; i<10000; ++i)
        {
            cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
            //my_mutex1.lock();//1
            std::lock_guard<std::mutex> sbguard(my_mutex1);//2  
            msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
            //my_mutex1.unlock();//1
        }
    }
    //补充写法
    //void inmsgrectqueue()
    //{
    //    for (int i = 0; i < 100; ++i)
    //    {
    //        cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
    //        {            
    //            std::lock_guard<std::mutex> sbguard(my_mutex1);//2  
    //            msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
    //        }//这里用大括号保证析构lock_guard在大括号结束时进行,达到提前解锁的目的
    //    }
    //}

    bool outmsglulproc(int &command)
    {
        std::lock_guard<std::mutex> sbguard(my_mutex1);//2  程序结束时析构会自动执行unlock()这段代码里有两个return所以可以替代两个unlock();
        //my_mutex1.lock();//1
        if(!msgrectqueue.empty())
        {
            //消息不为空
            int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
            msgrectqueue.pop_front();//移除第一个元素,但不返回;
            //my_mutex1.unlock();//1
            return true;
        }
        //my_mutex1.unlock();//1
        return false;
    }

    ////把数据从消息队列中取出的线程
    void outmsgrectqueue()
    {
        int command = 0;
        for (int i = 0; i<10000; ++i)
        {
            bool result = outmsglulproc(command);
            if(result==true)
            {
                cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
            }
            else
            {
                //消息队列为空
                cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
            }
            cout << "end" << endl;
        }
    }

private:
    std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex1;//创建一个互斥量(一把锁头)
    std::mutex my_mutex2; 
};
int main()
{
    A myobja;
    std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
    std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
    myinmsgobj.join();
    myoutmsgobj.join();

    //保护共享数据,操作的时候,某个线程用代码把共享数据锁住、操作数据、解锁
    //其他想操作共享数据的线程必须等待解锁,操作、解锁
    //互斥量(mutex)的基本概念
    //互斥量就是个类对象,理解成一把锁,多个线程尝试lock()成员函数来加锁这把锁头,只有一个线程能锁成功(成功的标志是lock函数返回)
    //如果没锁成功,那么流程卡再lock这里不断的尝试去锁这把锁头。
    //互斥量使用要小心,保护数据不多也不少,少了,没达到保护效果,多了,影响效率。

    //二.互斥量的用法
    //(2.1)lock(),unlock();
    //步骤:先lock(),操作共享数据,unlock()
    //lock和unlock要成对使用,二者调用次数要相同,每调用一次lock必然要调用一次unlock
    //有lock忘记unlock非常难排查
    //为了防止大家忘记unlock(),引入一个叫std::lock_guard的类模板,你忘记unlock不要紧,我替你unlock;
    //学习过智能指针(unique_ptr<>):你忘记释放内存不要紧,我帮你释放;

    //(2.2)std::lock_guard类模板:直接取代lock和unlock,就是说,用了lock_guard后,不能再用lock和unlock了,不过只能在
    //析构(比如函数return时)才会unlock,不够灵活
    return 0;
}

死锁演示:

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>

using namespace std;
vector<int> g_v = { 1,2,3 };//共享数据,只读
                            //线程入口函数
                            //void myprint(int inum)
                            //{
                            //    //cout << "myprint线程开始执行了,线程编号=" << inum << endl;
                            //    //cout<< "myprint线程结束执行了,线程编号=" << inum << endl;
                            //    cout << "id 为" << std::this_thread::get_id << "的线程打印g_v的值" << g_v[0] << g_v[1] << g_v[2] << endl;
                            //    return;
                            //}

class A
{
public:
    //把受到的消息(玩家命令)入到一个队列的线程
    void inmsgrectqueue()
    {
        for (int i = 0; i<100; ++i)
        {
            cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
            {
                my_mutex1.lock();//实际工程中这两个锁头不一定挨着,可能他们需要保护不同的数据共享块
                Sleep(1000);//程序执行太快的话这两个lock会同时执行,就不会发生死锁,这里加Sleep确保死锁的发生;
                my_mutex2.lock();
                msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
                my_mutex2.unlock();
                my_mutex1.unlock();
            }
        }
    }

    bool outmsglulproc(int &command)
    {
        my_mutex2.lock();
        my_mutex1.lock();
        if(!msgrectqueue.empty())
        {
            //消息不为空
            int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
            msgrectqueue.pop_front();//移除第一个元素,但不返回;
            my_mutex2.unlock();
            my_mutex1.unlock();
            return true;
        }
        my_mutex2.unlock();
        my_mutex1.unlock();
        return false;
    }

    ////把数据从消息队列中取出的线程
    void outmsgrectqueue()
    {
        int command = 0;
        for (int i = 0; i<10000; ++i)
        {
            bool result = outmsglulproc(command);
            if(result==true)
            {
                cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
            }
            else
            {
                //消息队列为空
                cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
            }
            cout << "end" << endl;
        }
    }

private:
    std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex1;//创建一个互斥量(一把锁头)
    std::mutex my_mutex2; 
};
int main()
{
    A myobja;
    std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
    std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
    myinmsgobj.join();
    myoutmsgobj.join();//三:死锁
    //张三:站在北京 等李四 ,不挪窝
    //李四:站在深圳 等张三, 不挪窝
    //C++中
    //比如我有两把锁(死锁这个问题是由至少两个锁头也就是两个互斥量才能产生);金锁,银锁。
    //两个线程A,B
    //1.比如线程A执行的时候这个线程先锁金锁,把金锁lock成功了,然后它去lock银锁
    //出现了上下文切换
    //2.线程B执行了,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会lock成功,线程B要去lock金锁..
    //此时,死锁就产生了
    //3.线程A 因为拿不到银锁头,流程走不下去(后边代码有解锁金锁头的但是流程走不下去,所以金锁头解不开)
    //4.线程B因为拿不到金锁头,流程走不下去(后边代码有解锁银锁头的但是流程走不下去,所以银锁头解不开)
    //线程互相等待陷入死锁

    //死锁解决方案
    //只要保证这两个互斥量上锁的顺序一致就不会死锁

    return 0;
}

 std::lock()

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>

using namespace std;

class A
{
public:
    //把受到的消息(玩家命令)入到一个队列的线程
    void inmsgrectqueue()
    {
        for (int i = 0; i<100; ++i)
        {
            cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
            {
                std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了.lock();
                msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
                my_mutex1.unlock();
                my_mutex2.unlock();
            }
        }
    }

    bool outmsglulproc(int &command)
    {
        std::lock(my_mutex2, my_mutex1);//两个互斥量先后没影响
        if(!msgrectqueue.empty())
        {
            //消息不为空
            int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
            msgrectqueue.pop_front();//移除第一个元素,但不返回;
            my_mutex2.unlock();
            my_mutex1.unlock();//1
            return true;
        }
        my_mutex2.unlock();
        my_mutex1.unlock();//1
        return false;
    }

    ////把数据从消息队列中取出的线程
    void outmsgrectqueue()
    {
        int command = 0;
        for (int i = 0; i<10000; ++i)
        {
            bool result = outmsglulproc(command);
            if(result==true)
            {
                cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
            }
            else
            {
                //消息队列为空
                cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
            }
            cout << "end" << endl;
        }
    }

private:
    std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex1;//创建一个互斥量(一把锁头)
    std::mutex my_mutex2; 
};
int main()
{
    A myobja;
    std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
    std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
    myinmsgobj.join();
    myoutmsgobj.join();
//(3.3)std::lock()函数模板,用于处理多个互斥量的时候才出场
    //能力:一次锁住两个或两个以上的互斥量(至少两个,多个不限。一个不行)
    //它不存在这种因为在多个线程中 因为锁的顺序问题导致死锁的风险问题
    //如果互斥量中有一个没锁柱,它就在那里等着,等所有互斥量都锁住,它才往下走(返回)
    //要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另一个没锁成功,则它立即把已经锁住的解锁。

    return 0;
}

 std::lock_guard和std::adopt_lock参数

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>

using namespace std;

class A
{
public:
    //把受到的消息(玩家命令)入到一个队列的线程
    void inmsgrectqueue()
    {
        for (int i = 0; i<100; ++i)
        {
            cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
            {
                std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了.lock()
                std::lock_guard<std::mutex> subguard1(my_mutex1, std::adopt_lock);
                std::lock_guard<std::mutex> subguard2(my_mutex2, std::adopt_lock);
                msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
            }
        }
    }

    bool outmsglulproc(int &command)
    {
        std::lock(my_mutex1, my_mutex2);//两个互斥量先后没影响
        std::lock_guard<std::mutex> subguard1(my_mutex1, std::adopt_lock);
        std::lock_guard<std::mutex> subguard2(my_mutex2, std::adopt_lock);
        if(!msgrectqueue.empty())
        {
            //消息不为空
            int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
            msgrectqueue.pop_front();//移除第一个元素,但不返回;
            return true;
        }
        return false;
    }

    ////把数据从消息队列中取出的线程
    void outmsgrectqueue()
    {
        int command = 0;
        for (int i = 0; i<100; ++i)
        {
            bool result = outmsglulproc(command);
            if(result==true)
            {
                cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
            }
            else
            {
                //消息队列为空
                cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
            }
            cout << "end" << endl;
        }
    }

private:
    std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex1;//创建一个互斥量(一把锁头)
    std::mutex my_mutex2; 
};
int main()
{
    A myobja;
    std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
    std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
    myinmsgobj.join();
    myoutmsgobj.join();
//(3.4)std::lock_guard和std::adopt_lock参数
    //std::adopt_lock是个结构体对象,起一个标记作用:作用就是表示这个互斥量已经lock()
    //不需要再std::lock_guard<std::mutex>构造函数里面对mutex对象进行lock了
    //总结:std::lock():一次锁定多个互斥量,谨慎使用(建议一个一个锁)

    return 0;
}

 

posted @ 2020-09-29 10:22  AI_ON  阅读(170)  评论(0)    收藏  举报