C++:并发与多线程(七)


)

condition_variable、wait、 notify_one、 notify_all

一 条件变量 std: condition_variable, wait(),notify _one()

1.condition_variable

condition_variable my_cond; //生成一个条件变量对象
std::condition_variable 是一个类,是一个和条件相关的一个类,就是等待一个条件达成,和互斥锁搭配使用,该类对象具有成员函数wait()和notify_one(),配合使用。

2.wait()

wait()用来等一个东西
参数一:需要配合的互斥锁
参数二:lambda表达式,即唤醒或睡眠wait()的条件

如果第二个参数即lambda的返回值为false,表面无法满足wait()下面的程序的条件,那么wait()将解锁互斥量,使生产者去生产任务,wait()堵塞到本行,即睡眠状态,一直到其他某个线程调用notify_one()激活wait(),激活wait(),wait()首先抢锁,抢到锁继续判断第二个参数的返回值,如果是false则继续解锁并睡眠,直到返回true,执行接下来的流程。
如果lambda返回true,那么wait()直接返回,执行wait()下面的操作。
如果wait没有第二个参数,那么和lambda默认返回false效果一样

 condition_variable my_cond; 
   //读取命令的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        while (true)
        {
            std::unique_lock<std::mutex> sbguard1(my_mutex1);
            //wait()用来等一个东西
            //如果第二个参数即lambda的返回值为false,那么wait将解锁互斥量,并堵塞到本行
            //如果lambda返回true,那么wait()直接返回
            // 一直到其他某个线程调用notify_one()函数为止。
            //如果wait没有第二个参数,那么和lambda默认返回false效果一样
            my_cond.wait(sbguard1,[this]{
                if (!msgRecvQueue.empty())
                    return true;
                return false;
            });
        }
    }

3.notify _one()

将一个wait唤醒,表示已经满足了消费者所需要的条件,激活wait进行后续拿锁和判断的操作。

   void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; i++)
        {
            {
                cout << "inMsgRecvQueue insert:" << i << endl;

                std::unique_lock<std::mutex>sbguard1 = rtn_unique_lock();

                msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列
                my_cond.notify_one(); //尝试把wait的线程唤醒,执行本行,则outMsgRecvQueue里边的wait就会被唤醒
            }
        }
    }

当其他线程用notify_one将此wait的状态状态唤醒后,wait开始做事
(1) 不断尝试获取互斥量锁,如果获取不到,则流程卡在wait不断获取互斥锁,如果获取到则wait继续执行上锁,实际上获取到锁就是上锁
(2.1)如果wait有第二个参数,则判断这个lambda,如果为false,那么wait又解锁,堵塞到本行,然后再休眠,等待再次被唤醒
(2.2)如果lambda返回为true,则wait返回,流程走下来,互斥锁还是锁定的
(2.3)如果没有第二个参数,则wait返回,流程走下去
流程走到下一行,执行具有互斥锁保护的操作,等待整个流程执行完毕,unique_lock会自动解锁,或者手动提前解锁,然后执行不需要保护的操作。

 //读取命令的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        while (true)
        {
            std::unique_lock<std::mutex> sbguard1(my_mutex1);
            //wait()用来等一个东西
            //如果第二个参数即lambda的返回值为false,那么wait将解锁互斥量,并堵塞到本行
            //如果lambda返回true,那么wait()直接返回
            // 一直到其他某个线程调用notify_one()函数为止。
            //如果wait没有第二个参数,那么和lambda默认返回false效果一样

            //当其他线程用notify_one将此wait的状态状态唤醒后,wait开始做事
            //(1) 不断尝试获取互斥量锁,如果获取不到,则流程卡在wait不断获取互斥锁,如果获取到则wait继续执行上锁,实际上获取到锁就是上锁
            //(2.1)如果wait有第二个参数,则判断这个lambda,如果为false,那么wait又解锁,堵塞到本行,然后再休眠,等待再次被唤醒
            //(2.2)如果lambda返回为true,则wait返回,流程走下来,互斥锁还是锁定的
            //(2.3)如果没有第二个参数,则wait返回,流程走下去
            my_cond.wait(sbguard1,[this]{
                if (!msgRecvQueue.empty())
                    return true;
                return false;
            });
              //流程走到这里,互斥锁一定是锁着的,msgRecvQueue里面肯定是有数据的
            command = msgRecvQueue.front();
            msgRecvQueue.pop_front();
            sbguard1.unlock();//提前解锁,因为unique_lock的灵活性,所以我们可以提前解锁,执行后续操作
            cout<<"outMsgRecvQueue get :"<<command<<endl;
        }
    }

整体代码


#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include  <mutex>
#include <condition_variable>
using namespace std;





class A
{
public:
    //把玩家命令放入到一个队列的进程
    std::unique_lock<std::mutex>rtn_unique_lock()
    {
        std::unique_lock<std::mutex>temguard(my_mutex1);
        return temguard;
        //从函数返回一个局部的unique_lock对象是可以的,移动构造函数
        //返回这种局部对象temguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数

    }
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 5; i++)
        {
            {
                cout << "inMsgRecvQueue insert:" << i << endl;

                std::unique_lock<std::mutex>sbguard1 = rtn_unique_lock();

                msgRecvQueue.push_back(i);     //假设数字i为命令 放入队列
                my_cond.notify_one(); //尝试把wait的线程唤醒,执行本行,则outMsgRecvQueue里边的wait就会被唤醒
            }
        }
    }


    //读取命令的线程
    void outMsgRecvQueue() {
        int command = 0;
        while (true) {
            std::unique_lock<std::mutex> sbguard1(my_mutex1);
            //wait()用来等一个东西
            //如果第二个参数即lambda的返回值为false,那么wait将解锁互斥量,并堵塞到本行
            //如果lambda返回true,那么wait()直接返回
            // 一直到其他某个线程调用notify_one()函数为止。
            //如果wait没有第二个参数,那么和lambda默认返回false效果一样

            //当其他线程用notify_one将此wait的状态状态唤醒后,wait开始做事
            //(1) 不断尝试获取互斥量锁,如果获取不到,则流程卡在wait不断获取互斥锁,如果获取到则wait继续执行上锁,实际上获取到锁就是上锁
            //(2.1)如果wait有第二个参数,则判断这个lambda,如果为false,那么wait又解锁,堵塞到本行,然后再休眠,等待再次被唤醒
            //(2.2)如果lambda返回为true,则wait返回,流程走下来,互斥锁还是锁定的
            //(2.3)如果没有第二个参数,则wait返回,流程走下去

            my_cond.wait(sbguard1, [this] {
                if (!msgRecvQueue.empty())
                    return true;
                return false;
            });
            //流程走到这里,互斥锁一定是锁着的,msgRecvQueue里面肯定是有数据的
            command = msgRecvQueue.front();
            msgRecvQueue.pop_front();
            sbguard1.unlock();//提前解锁,因为unique_lock的灵活性,所以我们可以提前解锁
            cout<<"outMsgRecvQueue get :"<<command<<endl;
        }
    }
private:

    std::list<int>msgRecvQueue;
    mutex my_mutex1;           //创建一个互斥量
    condition_variable my_cond;  //生成一个条件变量对象

};

int main()
{
   A myobja;
    thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
    thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    myOutnMsg.join();
    myInMsgObj.join();
}

二 上述代码深入思考

关键点:
(1)wait被唤醒后,还是需要拿锁,而且wait并不一定拿到锁,因为生产者也一直在拿锁,如果多个消费者,则生产者可能拿到锁,或者只有一个消费者能拿到锁。
(2)wait拿到锁后,生产者可能产生了不止一个任务,因为生产者也一直在参与锁的竞争,可能连续拿好几次。
(3)unique_lock可以提前解锁,但是如果在解锁后执行一个稍微耗时的操作,则此时生产者已经拿到锁,生产任务后执行了唤醒操作,但消费者没有卡在wait处,则此次唤醒失败。即notify_one不是每次都能唤醒没有处在睡眠状态的wait。
(4)生产者产生任务速度大于消费者消费速度,导致很多任务被积压

三 notify_all()

notify_one()只能通知一个线程,使其被唤醒。
notify_all :将所有的线程中的wait都被唤醒。
虽然是将所有处于睡眠的wait()唤醒,但还是只能由一个线程拿到互斥锁,所以看起来的效果与notify_one相同,但可以用于不同的工作场景。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Michael.Scofield

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

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

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

打赏作者

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

抵扣说明:

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

余额充值