美文网首页
多线程学习(七)

多线程学习(七)

作者: lxr_ | 来源:发表于2021-11-21 20:37 被阅读0次

    问题:使用上一篇讲得双重锁定(双重检查)可以提高效率,避免先创建锁再判断而引起的效率低下问题
    但是不断地判断if条件是否满足,也造成了一定的效率问题
    解决

    一:条件变量std::conidtion_variable,wait(),notify_one()

    解释:有两个线程A和线程B,线程A等待一个条件满足(通知)才继续执行,线程B专门向内存中写入数据,并发布通知
    std::condition_variable是一个和条件相关的类作用:等待一个条件达成(等到通知),需要和互斥量配合使用**
    使用的时候生成这个类的对象

    继续使用前面的例子进行测试,即包含两个线程(一个写线程和一个读线程)

    #include "stdafx.h"
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <list>
    using namespace std;
    
    class A
    {
    public:
        //写线程:把收到的消息(玩家命令)放入一个队列
        void inMsgRecvQueue()
        {
            for (int i = 0; i < 100000; i++)
            {
                cout << "inMsgRecvQueue(),插入一个元素" << i << endl;
    
                std::unique_lock<std::mutex> myUniqueLock(myMutex); //使用unique_lock替代lock_guard
    
                msgRecvQueue.push_back(i);    //假设i就是收到的命令,存入消息队列
                myCond.notify_one();         
                //尝试把wait()的线程唤醒,执行完这一行后
                //outMsgRecvQueue线程里的wait()就会被唤醒,唤醒之后的事情后续说明.....
    
            }
            return;
        }
    
        //读线程:把收到的命令取出
        void outMsgRecvQueue()
        {
            int command;
            while (true)
            {
                std::unique_lock<std::mutex> myUniqueLock(myMutex);
    
                //wait()用来等待某一个消息
                //如果第二个参数lambda表达式返回值为true,wait()直接返回,程序向下执行
                //如果第二个参数的lambda表达式返回值为false,那么wait()将解锁互斥量,并堵塞到本行
                //堵塞到其他某个线程调用notify_one()成员函数为止
                //如果wait()无第二个参数,即为myCond.wait(myUniqueLock),
                //此情况那么就和第二个参数lambda表达式返回false的效果一样
                //也就是wait()将解锁互斥量,并堵塞到本行,并堵塞到其他某个线程调用notify_one()成员函数为止
                //当其他线程使用notify_one()将此wait()(原来为堵塞状态)唤醒后,wait()恢复执行,如下:
                //a)wait()不断尝试重新获取互斥量(锁),如果获取不到,那么流程还是卡在wait()处等着获取互斥量(锁)
                //如果获取到锁(加锁),wait()就继续执行下一步b
                //b)
                //b.1)如果wait有第二个参数lambda表达式,判断lambda表达式返回false
                //则wait()又对互斥量解锁,然后又堵塞到此处等待再次被notify_one唤醒
                //b.2)如果lambda表达式为true,则wait()返回,流程继续执行(此时互斥锁被锁着)
                //b.3)如果wait()没有第二个参数,则wait()返回,流程继续执行
                myCond.wait(myUniqueLock, 
                    [this] {                            //一个lambda表达式就是一个可调用对象(函数)
                    if (!msgRecvQueue.empty())
                        return true;
                    return false;
                });
    
                //程序只要能执行到此处,互斥量一定是锁着的,且msgRecvQueue至少有一条数据的
                command = msgRecvQueue.front();       //返回第一个元素
                msgRecvQueue.pop_front();              //移除第一个元素,但不返回
                myUniqueLock.unlock();                 
                //因为unique_lock的灵活性,提前解锁后(以免锁住太长时间),写线程可以执行了
                cout << "outMsgRecvQueue()执行,取出一个命令" << command << endl;
    
            }
        }
    
    private:
        list<int> msgRecvQueue;      //存放玩家发送的命令队列(共享数据)
        mutex myMutex;              //创建了一个互斥量
        std::condition_variable myCond;//创建一个条件变量对象
    };
    int main(int argc, char** argv)
    {
        A obj;
    
        std::thread myOutMsgObj(&A::outMsgRecvQueue, &obj);     //第二个参数为引用,保证子线程操作的是主线程中的obj
    
        std::thread myInMsgObj(&A::inMsgRecvQueue, &obj);       //第二个参数为引用类型
    
        myOutMsgObj.join();                                     //阻塞主线程并等待子线程执行完毕
        myInMsgObj.join();
    
        return 0;
    }
    

    上面程序执行时看似没有错误之处,但有不完美的地方:
    1.如果写线程inMsgRecvQueue先执行完后,而读线程outMsgRecvQueue还没执行完,则写线程不能执行notify_one通知读线程读线程就会一直卡在wait()函数处。
    2.如果读线程读到数据后,还要执行一系列其他程序,耗时较长,而写线程又再一次执行到notify_one通知读线程,而读线程并没有卡在wait()处,而是在执行其他程序,那么notify_one就可能没有效果

    二.notify_all()

    notify_one()只能通知一个线程,使得一个线程被唤醒
    当我们创建多个读线程outMsgRecvQueue时,如果仍使用notify_one,只能唤醒其中一个线程
    所以怎么同时唤醒多个线程呢?使用notify_all()
    虽然两个线程都被唤醒了,但是在wait()函数中,两个线程都要尝试获取锁,但是只有一个能获取到锁未获取到锁的线程还是得卡在wait()函数中
    在下面的测试中,再创建一个读线程,使用notify_one和notify_all都是一样的效果,都只有一个线程继续执行

    class A
    {
    public:
        //写线程:把收到的消息(玩家命令)放入一个队列
        void inMsgRecvQueue()
        {
            for (int i = 0; i < 100000; i++)
            {
                cout << "inMsgRecvQueue(),插入一个元素" << i << endl;
    
                std::unique_lock<std::mutex> myUniqueLock(myMutex); //使用unique_lock替代lock_guard
    
                msgRecvQueue.push_back(i);    //假设i就是收到的命令,存入消息队列
    
                //myCond.notify_one();
                //尝试把wait()的线程唤醒,执行完这一行后,outMsgRecvQueue线程里的wait()就会被唤醒.
    
                myCond.notify_all();       //尝试唤醒所有wait()的线程
    
            }
            return;
        }
    
        //读线程:把收到的命令取出
        void outMsgRecvQueue()
        {
            int command;
            while (true)
            {
                std::unique_lock<std::mutex> myUniqueLock(myMutex);
    
                //wait()用来等待某一个消息
                //如果第二个参数lambda表达式返回值为true,wait()直接返回
                //如果第二个参数的lambda表达式返回值为false,那么wait()将解锁互斥量,并堵塞到本行
                //堵塞到其他某个线程调用notify_one()成员函数为止
                //如果wait()无第二个参数,即myCond.wait(myUniqueLock),那么就第二个参数lambda表达式返回false的效果一样
                //也就是wait()将解锁互斥量,并堵塞到本行,并堵塞到其他某个线程调用notify_one()成员函数为止
                //当其他线程使用notify_one()将此wait()(原来为堵塞状态)唤醒后,wait()恢复执行
                //a)wait()不断尝试重新获取互斥量(锁),如果获取不到,那么流程还是卡在wait()处等着获取互斥量(锁)
                //如果获取到锁(加锁),wait()就继续执行下一步b
                //b)
                //b.1)如果wait有第二个参数lambda表达式,如果判断lambda表达式返回false
                //wait()又对互斥量解锁,然后又堵塞到此处等待再次被notify_one唤醒
                //b.2)如果lambda表达式为true,则wait()返回,流程继续执行(此时互斥锁被锁着)
                //b.3)如果wait()没有第二个参数,则wait()返回,流程继续执行
                myCond.wait(myUniqueLock,
                    [this] {                            //一个lambda表达式就是一个可调用对象(函数)
                    if (!msgRecvQueue.empty())
                        return true;
                    return false;
                });
    
                //程序只要能执行到此处,互斥量一定是锁着的,且msgRecvQueue至少有一条数据的
                command = msgRecvQueue.front();       //返回第一个元素
                msgRecvQueue.pop_front();              //移除第一个元素,但不返回
                cout << "outMsgRecvQueue()执行,取出一个命令" << command
                     << "threadid=" << std::this_thread::get_id() << endl;
                myUniqueLock.unlock();                 
                //因为unique_lock的灵活性,提前解锁后(以免锁住太长时间),写线程可以执行了
                
    
            }
        }
    
    private:
        list<int> msgRecvQueue;      //存放玩家发送的命令队列(共享数据)
        mutex myMutex;              //创建了一个互斥量
        std::condition_variable myCond;//创建一个条件变量对象
    };
    int main(int argc, char** argv)
    {
        A obj;
    
        std::thread myOutMsgObj1(&A::outMsgRecvQueue, &obj);
                                                                //第二个参数为引用,保证子线程操作的是主线程中的obj
        std::thread myOutMsgObj2(&A::outMsgRecvQueue, &obj);    //再创建一个读线程
    
        std::thread myInMsgObj(&A::inMsgRecvQueue, &obj);       //第二个参数为引用
    
        myOutMsgObj1.join();                                     //阻塞主线程并等待子线程执行完毕
        myOutMsgObj2.join();                                     //阻塞主线程并等待子线程执行完毕
        myInMsgObj.join();
    
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:多线程学习(七)

          本文链接:https://www.haomeiwen.com/subject/uafmtrtx.html