c++多线程编程——条件变量
1.前言
多线程中有时需要线程间的通讯,也就是一个线程需要知道它所需要资源准备好了没,需要负责生产的线程通知他资源已经准备好了。这时就要用到条件变量了,信号量等。今天我们来讲条件变量(condition_variable),条件变量一般配合互斥锁使用,他通常还会配合一个共享变量来使用,去通知线程已经满足运行的条件。
以下是代码会用到的头文件
//使用c++14规则
#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>
2.条件变量的使用
2.1条件变量的基础
条件变量(condition_variable)是不可移动,不可复制的,条件变量的创建不需要任何参数,像创建互斥锁一样。std::condition_variable cv;
2.2条件变量的函数
条件变量中有几个成员函数用于实现通知其他条件变量,等待其他条件变量的消息等功能,以下是几个成员函数-
notify_one(),通知一个线程的条件变量。相当于唤醒一个因为条件变量而停止的线程让他起来工作。
-
notify_all(),通知所有线程。相当于唤醒所有因为条件变量而停止的线程。
-
wait(unique_lock
& _Lck) ,传入一个被独占锁(unique_lock)管理的互斥锁。等待被唤醒。 -
wait(unique_lock
& _Lck, _Predicate _Pred) ,传入两个参数,一个是被独占锁(unique_lock)管理的互斥锁,另一个是个谓词。与上一个不同的是,被唤醒后,函数还需要判断传入的谓词是否为true.
_Predicate _Pred表示传入一个谓词,一般是传入一个返回值为bool的函数的函数名。还可以传入函数指针,函数对象,Lambda表达式(匿名函数)。注意!!!这些传入的东西的返回值得是bool类型的值。
- wait_for(unique_lock
& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time) ,这里要传入两个参数,一个是被独占锁(unique_lock)管理的互斥锁,第二个则是一个时间范围。
如果在时间范围内收到信号被唤醒则返回std::cv_status::no_timeout,否则则会超时返回std::cv_status::timeout.
- wait_for(unique_lock
& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time, _Predicate _Pred) ,这个函数要求传入三个参数,相比于上面一个函数多了个谓词。这表明这个函数在时间范围内被唤醒后还会判断谓词是否为true。
他会先判断谓词是否为true,再判断是否超时,如果超时返回谓词判断的结果。否则返回true。
- wait_until(unique_lock
& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time) ,传入两个参数,与之前不同,这次是传入时间点。
如果在时间点到达前收到信号被唤醒则返回std::cv_status::no_timeout,否则则会超时返回std::cv_status::timeout.
-
wait_until(unique_lock
& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time, _Predicate _Pred) ,这个函数要求传入三个参数,相比于上面一个函数多了个谓词。这表明这个函数在时间点到达前被唤醒后还会判断谓词是否为true。 -
_Register(unique_lock
& _Lck, int* _Ready) ,为防止线程意外终止导致锁未释放,需要调用 _Register 注册清理逻辑。_Ready 作为标志位,指示资源是否已释放。 -
_Unregister(mutex& _Mtx),传入一个互斥锁,取消线程退出时对互斥锁的自动释放注册
下面是一些代码实现上面的函数
std::mutex mtx; //互斥锁
std::condition_variable cv;
bool flag = false; //条件变量
void thread_func_custom(int i) { //基于条件变量的互斥锁
std::unique_lock<std::mutex> lck(mtx);
while (!flag) {
if (cv.wait_for(lck, std::chrono::seconds(1)) == std::cv_status::timeout) {//条件变量因为时间耗尽而被唤醒
std::cout << ".";
std::cout.flush();
}
}
std::cout << std::this_thread::get_id() << "-" << i << std::endl;
}
主函数如下
点击查看代码
``` int main() { std::thread t1(thread_func_custom, 10); std::thread t2(thread_func_product); t1.join(); t2.join(); return 0; } ```结果类似
..41832-10
下面这段代码因为添加了一个谓词判断所以不会像上面的代码一样打印 "...." ,而是直接阻塞住。
std::mutex mtx; //互斥锁
std::condition_variable cv;
bool flag = false; //条件变量
void thread_func_custom(int i) { //基于条件变量的互斥锁
std::unique_lock<std::mutex> lck(mtx);
while (!flag) {
if (cv.wait_for(lck, std::chrono::seconds(1), []() {return flag; })) {//阻塞
std::cout << ".";
std::cout.flush();
}
}
std::cout << std::this_thread::get_id() << "-" << i << std::endl;
}
std::mutex mtx; //互斥锁
std::condition_variable cv;
bool flag = false; //条件变量
void thread_func_custom(int i) { //基于条件变量的互斥锁
std::unique_lock<std::mutex> lck(mtx);
while (!flag) {
cv.wait(lck); //等待条件成立
//cv.wait(lck, []() {return flag; }); //Lambda表达式(匿名函数)
}
std::cout << std::this_thread::get_id() << "-" << i << std::endl;
}
void thread_func_product() {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::unique_lock<std::mutex> lck(mtx);
flag = true; //条件成立
//cv.notify_all(); //通知所有等待线程
cv.notify_one(); //通知一个等待线程
}
3.条件变量的使用注意
- 条件变量的虚假唤醒
其一是由于notify_all()函数唤醒所有等待的线程。但是只有一个线程得到了数据,其他线程被唤醒但是并未得到数据。其二系统会莫名唤醒正在阻塞队列的线程。这就导致了虚假唤醒。
针对虚假唤醒的情况的解决方法一般是添加while循环来判断。
while (!flag) {
//条件变量等待函数
}

以上为我在学习过程中整理的知识点,如有哪里说错了感谢指出
一部分代码参考【C++11 多线程编程-小白零基础到手撕线程池】https://www.bilibili.com/video/BV1d841117SH?p=8&vd_source=dccc0abff62c8559f0a5ed0bce39dec2

浙公网安备 33010602011771号