【C++】条件变量condition_variable
文章目录
- 1. 条件变量定义及特点
- 2. 代码示例
- 3. wait方法
- 4. wait_for方法
- 5. notify_all和notify_one
- 6. 思考
1. 条件变量定义及特点
条件变量 用于在线程之间协调共享资源的访问。它允许一个线程等待特定条件的满足(如某个值的变化),而另一个线程在条件满足时通知(或唤醒)等待的线程。这种机制可以防止线程忙等待,从而提高系统效率。
条件变量特点:
1. 等待和通知机制:线程可以等待某个条件的改变,而不需要一直占用CPU
2. 与互斥锁配合使用, 以保护共享数据的访问
3. 线程通信:实现线程之间的高效通信
注意事项: 条件变量必须与互斥锁一起使用,以确保在检查条件(while(!ready))和修改共享数据(ready=true)时的线程安全,如果省略了互斥锁,可能导致数据竞争和其它并发问题
2. 代码示例
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx; // 定义互斥锁
condition_variable cv; // 创建一个条件变量实例
bool flag = false; // 全局变量flag
void myPrint(int i){
unique_lock<mutex> lck(mtx);
// while (!flag){
// cv.wait(lck); // 条件不成立 进入阻塞状态 释放CPU资源 释放掉锁 等待别人的唤醒
// }
cv.wait(lck, [](){return flag;}); // 这个写法与上面while效果相同
cout<< this_thread::get_id() << "-" << i <<endl;
}
void updateflag(){
cout<< "this is update" <<endl;
this_thread::sleep_for(3s);
unique_lock<mutex> lck(mtx);
flag = true;
cv.notify_all();
}
int main(int argc, char** argv){
vector<thread> mybox;
for(size_t i=0; i<10; i++){
mybox.emplace_back(myPrint, i);
}
updateflag();
for(thread& t: mybox){
t.join();
}
return 0;
}
3. wait方法
wait方法相当于阻塞当前线程,直到另一个线程调用同一个 std:condition_variable 实例的notify_one 或 notify_all方法,或者直到指定的谓词函数(即条件)返回 true。如果你没有通知,即使条件为TRUE,它也不会被执行。如果,你通知了,但是条件不成立,它仍然被阻塞,不会执行。它的执行顺序是 先通知 再检测条件。
参数:
std::unique_lock <std:mutex> & lock:
一个与互斥锁相关联的 std:unique_lock
对象。调用 wait 时,这个锁会被自动释放,允许其他线程获取该锁并执行。当 wait 返回时,锁会被重新获取。
工作原理:
- 释放锁:当线程调用 wait 方法时,它会首先释放与 std::unique_lock 关联的互斥锁。这是为了允许其他线程可以访问和修改与条件变量相关的共享数据。
- 进入等待队列:释放锁后,线程会进入条件变量的等待队列中,进入阻塞状态。此时线程不再消耗CPU时间,直到被唤醒。
- 等待条件或通知:线程在等待队列中等待,直到两个条件之一发生:
3.1 另一个线程调用了同一个条件变量的 notify_one 或 notify_all方法,并且该线程是等待队列中的第一个线程(对于notify_one)或等待队列中的所有线程(对于notify_all)
3.2 谓词函数 pred 返回 true
- 重新获取锁:当线程被唤醒后(无论是由于收到通知还是条件成立),它会尝试重新获取之前释放的互斥锁。如果此时锁已经被其他线程持有,则该线程会阻塞在互斥锁上,直到获得锁。
- 检查条件:获得锁后,线程会再次调用谓词函数 pred 来检查条件是否成立。如果条件不成立(即 pred 返回false),则线程会重新进入等待队列,并释放锁,继续等待。这个过程会不断重复,直到条件成立
- 继续执行:如果条件成立(即pred返回true),则线程会退出wait方法,并继续执行后续的代码
注意事项:
- wait方法必须在已经锁定互斥锁的情况下调用,否则会抛出
std::system_error
异常 - 在调用wait方法之前,应该确保与条件变量相关的共享数据已经被初始化,并且已经设置了适当的条件
- 由于wait方法会释放和重新获取锁,因此在使用wait方法时应该确保没有其它线程会在等待期间修改与条件变量相关的共享数据,以避免竞态条件
4. wait_for方法
- wait_for方法与wait方法类似,但它允许你指定一个超时时间。如果在这段时间内条件没有满足,并且没有收到唤醒信号,那么wait_for会返回,并且线程会重新获取互斥锁。
- wait_for方法接受一个时间间隔作为参数,表示线程愿意等待的最长时间。这个时间间隔可以是 std::chrono 库中定义的任何时间单位。
- wait_for的返回值是一个
std::cv_statu
枚举值,表示等待操作的结果可能的返回值包括:
std::cv_status::no_timeout
:表示等待操作成功完成,即在超时时间内条件被满足或收到了唤醒信号
std::cv_status::timeout
:表示等待操作因超时而结束,条件没有被满足且没有收到唤醒信号
5. notify_all和notify_one
notify_all()和 notify_one()是条件变量(std::condition_variable)的两个成员函数,用于唤醒等待条件变量的线程。
a. notify_all():
作用: 唤醒所有等待该条件变量的线程。
使用场景: 适用于多个线程需要同时检査条件的情况。通常用于广播通知,让所有等待的线程都重新检查条件。
b. notify_one():
作用: 唤醒一个等待该条件变量的线程。
使用场景: 适用于只有一个线程需要处理条件的情况。通常用于优先级调度或队列处理,让其中一个线程获得执行权
6. 思考
1. 如果调用notify_one()次数超过等待通知线程数会怎么样?
如果调用 notify_one() 的次数超过了等待的线程数,则多余的通知将没有效果。 notify_one() 只会唤醒当前在等待队列中的一个线程,并不会将通知累积。例如有 2 个线程在等待,但调用了 3 次 notify_one(),则只有 2 个线程会被唤醒,剩余的 1 次通知将被丢弃
2.如果使用notify_all()进行通知时,没有在等待的线程会怎样?
调用notify_all() 时,如果没有线程在 wait() 状态,那么通知将直接丢失
3.如果有多个线程在等待,notify_one()会通知哪个线程?
当有多个线程在等待同一个条件变量时,notify_one() 会随机唤醒其中一个线程,但具体唤醒哪一个线程由系统决定。一般来说,唤醒哪个线程取决于操作系统的调度策略和线程的等待顺序。C++ 标准并没有定义确切的唤醒顺序,因此它具有不确定性,可能因系统和实现的不同而有所变化。
本文参考:https://www.bilibili.com/video/BV1E4421D76e?spm_id_from=333.788.player.switch&vd_source=cf0b4c9c919d381324e8f3466e714d7a