std::condition_variable::notify_one()伪唤醒的一个例子

伪唤醒的一个例子:

//https://en.cppreference.com/w/cpp/thread/condition_variable
//https://www.runoob.com/cplusplus/cpp-date-time.html
#include<iostream>
#include<string>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<chrono>
int main(int argc,char *argv[]){
    std::mutex mut;
    std::condition_variable cv;
    std::string data;
    bool bready(false);
    bool bprocessed(false);
    auto tfunc=[&](std::string id)->void{
        std::unique_lock<std::mutex> lk(mut);
        if(!bready){
            bool bpredicate=cv.wait_for(lk,dur,[&]{return bready;}); //传递捕获
            if(bpredicate)std::cout<<"子线程"<<id<<"消费bready\n";
            else std::cout<<"子线程"<<id<<"wait_for(bready)超时!\n";
        }
        //休眠线程被唤醒后要持锁继续执行
        std::cout<<"子线程"<<id<<"消费bready\n";
        //std::chrono::steady_clock::time_point nowt=std::chrono::high_resolution_clock::now();
        //std::chrono::system_clock::time_point nowt2=std::chrono::system_clock::now();
        std::time_t nowt3(std::time(0));
        char *cstr=new char[1<<10];
        cstr=std::ctime(&nowt3);
        data+=std::string(cstr)+"|_>子线程";
        data+=id;
        data+="处理数据\n";

        bprocessed=true;
        std::cout<<"子线程"<<id<<"生产bprocessed\n";
        lk.unlock();
        std::cout<<"子线程"<<id<<"notify_one()\n";
        cv.notify_one();    //notify_one()只能唤醒至多一个(或没有)在此条件变量上休眠的消费者线程
        // cv.notify_all();
    };

接上:


    std::thread worker(tfunc,"thread 1 ");  //子线程1首先休眠,首先被唤醒
    std::thread worker2(tfunc,"thread 2 "); //子线程2稍后休眠,稍后被唤醒
    std::time_t nowt3(std::time(0));
    char *cstr=new char[1<<10];
    cstr=std::ctime(&nowt3);
    data=std::string(cstr)+"|_>主线程初始化数据\n";
    {
        std::lock_guard<std::mutex> lk(mut);
        bready=true;
        std::cout<<"主线程生产bready\n";
    }
    std::cout<<"主线程notify_one()\n";
    cv.notify_one();  //notify_one()只能唤醒至多一个(或没有)在此条件变量上休眠的消费者线程
    {
        std::unique_lock<std::mutex> lk(mut);
        cv.wait(lk,[&]{return bprocessed;});    //主线程最后休眠,最后被唤醒
        std::cout<<"主线程消费bprocessed\n";
    }
    std::cout<<"\n回到主线程,data=\n"<<data<<"\n";
    worker.join();
    worker2.join();
}

如果在同一个条件变量上先后等待并休眠了多个线程( cv.wait(std::mutex,func) ),那么一旦该条件变量发出单向通知cv.notify_one(),总是唤醒当前时刻在该条件变量上最早进入休眠的线程,剩下的线程继续休眠。如果条件变量梳理不清,就会产生“期望线程A唤醒线程B,但实际上由线程C唤醒了线程B”的"伪唤醒"行为,因此要厘清各个条件变量的职能范围。

执行结果:


HaypinsMBP:MultiThread haypin$ clang++ -g spurious-wakeup.cpp -std=c++11 -o main
HaypinsMBP:MultiThread haypin$ ./main
主线程生产bready               #线程1首先开启并首先休眠,线程2稍后开启则次之休眠,主线程在检查bprocessed共享变量后最后进入休眠
主线程notify_one()            #主线程唤醒了最先启动也是最先休眠的线程1
子线程thread 1 消费bready      #线程1唤醒后持锁继续执行,线程2仍旧休眠
子线程thread 1 生产bprocessed
子线程thread 1 notify_one()   #线程1唤醒"条件变量cv的休眠线程队列头部的"线程2
子线程thread 2 消费bready      #线程2唤醒后持锁继续执行
子线程thread 2 生产bprocessed
子线程thread 2 notify_one()   #线程2唤醒"条件变量cv的休眠线程队列头部的"主线程
主线程消费bprocessed

回到主线程,data=
Mon Feb  1 20:04:06 2021
|_>主线程初始化数据
Mon Feb  1 20:04:06 2021
|_>子线程thread 1 处理数据
Mon Feb  1 20:04:06 2021
|_>子线程thread 2 处理数据

HaypinsMBP:MultiThread haypin$

如果将主线程唤醒子线程的条件变量cv与子线程唤醒主线程的条件变量cv2区分开就不会产生"伪唤醒"行为了:
 

#include<iostream>
#include<string>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<chrono>
int main(int argc,char *argv[]){
    std::mutex mut;
    std::condition_variable cv,cv2;
    std::string data;
    bool bready(false);
    bool bprocessed(false);
    auto tfunc=[&](std::string id)->void{
        std::unique_lock<std::mutex> lk(mut);
        if(!bready){
            bool bpredicate=cv.wait_for(lk,dur,[&]{return bready;}); //传递捕获
            if(bpredicate)std::cout<<"子线程"<<id<<"消费bready\n";
            else std::cout<<"子线程"<<id<<"wait_for(bready)超时!\n";
        }
        //休眠线程被唤醒后要持锁继续执行
        std::cout<<"子线程"<<id<<"消费bready\n";

        std::time_t nowt3(std::time(0));
        char *cstr=new char[1<<10];
        cstr=std::ctime(&nowt3);
        data+=std::string(cstr)+"|_>子线程";
        data+=id;
        data+="处理数据\n";

        bprocessed=true;
        std::cout<<"子线程"<<id<<"生产bprocessed\n";
        lk.unlock();
        std::cout<<"子线程"<<id<<"notify_one()\n";
        cv2.notify_one();    //notify_one()只能唤醒至多一个(或没有)在此条件变量上休眠的消费者线程
        // cv.notify_all();
    };

接上:


    std::thread worker(tfunc,"thread 1 ");  //子线程1首先休眠,首先被唤醒
    std::thread worker2(tfunc,"thread 2 "); //子线程2稍后休眠,稍后被唤醒
    std::time_t nowt3(std::time(0));
    char *cstr=new char[1<<10];
    cstr=std::ctime(&nowt3);
    data=std::string(cstr)+"|_>主线程初始化数据\n";
    {
        std::lock_guard<std::mutex> lk(mut);
        bready=true;
        std::cout<<"主线程生产bready\n";
    }
    std::cout<<"主线程notify_one()\n";
    cv.notify_one();  //notify_one()只能唤醒至多一个(或没有)在此条件变量上休眠的消费者线程
    {
        std::unique_lock<std::mutex> lk(mut);
        cv2.wait(lk,[&]{return bprocessed;});    //主线程最后休眠,最后被唤醒
        std::cout<<"主线程消费bprocessed\n";
    }
    std::cout<<"\n回到主线程,data=\n"<<data<<"\n";
    worker.join();
    worker2.join();
}

执行输出:

HaypinsMBP:MultiThread haypin$ ./main
主线程生产bready
主线程notify_one()
子线程thread 1 消费bready
子线程thread 1 生产bprocessed
子线程thread 1 notify_one()
主线程消费bprocessed

回到主线程,data=
Mon Feb  1 20:49:42 2021
|_>主线程初始化数据
Mon Feb  1 20:49:42 2021
|_>子线程thread 1 处理数据

^C
HaypinsMBP:MultiThread haypin$

此时线程1不会"伪唤醒"线程2,而是如所期地唤醒主线程,而线程2则一直休眠,所以示例ctrl-C强行结束了死休眠的线程2;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值