伪唤醒的一个例子:
//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;