C++ 多线程同步实战:条件变量(condition_variable)
💡 学习目标:掌握 C++ 标准库中条件变量的使用方法,理解条件变量与互斥锁的协同工作机制,能够解决多线程间的等待 - 通知问题。 💡 : 的核心接口、 与 / 的配合使用、生产者 - 消费者模型的实现。
C++ 条件变量用于解决多线程等待通知问题,避免轮询导致的 CPU 浪费。核心接口包括 wait、notify_one 和 notify_all,需配合 std::unique_lock 使用。通过带条件的 wait 可防止虚假唤醒。典型应用为生产者 - 消费者模型,支持多生产者和多消费者场景下的线程协作与资源管理。

💡 学习目标:掌握 C++ 标准库中条件变量的使用方法,理解条件变量与互斥锁的协同工作机制,能够解决多线程间的等待 - 通知问题。 💡 : 的核心接口、 与 / 的配合使用、生产者 - 消费者模型的实现。
std::condition_variablewait()notify_one()notify_all()在多线程编程中,我们经常会遇到线程需要等待某个条件满足后再执行的场景。比如生产者线程生产数据后,消费者线程才能消费;队列不为空时,消费者才能从中取数据。如果仅用互斥锁实现,消费者线程只能不断轮询检查条件,这会造成 CPU 资源的浪费。
⚠️ 注意事项:单纯的轮询会导致 CPU 空转,降低程序运行效率,条件变量就是为解决这类问题而生的。
举个简单的轮询反例,消费者不断检查队列是否有数据:
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
using namespace std;
queue<int> data_queue;
mutex mtx;
// 生产者
void producer(){
for(int i = 1; i <= 5; ++i){
lock_guard<mutex> lock(mtx);
data_queue.push(i);
cout << "生产者生产数据:" << i << endl;
}
}
// 消费者(轮询方式)
void consumer(){
while(true){
lock_guard<mutex> lock(mtx);
if(!data_queue.empty()){
int data = data_queue.front();
data_queue.pop();
cout << "消费者消费数据:" << data << endl;
if(data == 5) break;
}
// 没有数据时,依然会不断循环检查,浪费 CPU
}
}
int main(){
thread t_producer(producer);
thread t_consumer(consumer);
t_producer.join();
t_consumer.join();
return 0;
}
运行该程序,消费者线程在队列空的时候会一直循环检查,造成不必要的 CPU 开销。
C++11 标准库在 <condition_variable> 头文件中提供了 std::condition_variable 类,它需要与 std::mutex 配合使用,实现线程间的高效等待与通知。
std::condition_variable 的核心接口notify_one() 或 notify_all() 唤醒。pred 条件为 false 时才会阻塞。std::unique_lock 的原因std::condition_variable 的 wait() 函数要求传入 std::unique_lock,而不是 std::lock_guard。这是因为 wait() 过程中需要临时释放锁,而 std::unique_lock 支持手动解锁和加锁,std::lock_guard 仅支持构造加锁、析构解锁,无法满足需求。
✅ 核心结论:条件变量必须与 std::unique_lock 配合使用,才能实现等待时释放锁、唤醒后重新加锁的逻辑。
我们使用 std::condition_variable 改造 49.1 节的轮询反例,实现高效的生产者 - 消费者模型:
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;
queue<int> data_queue;
mutex mtx;
condition_variable cv;
bool is_produced = false;
// 生产完成标志
// 生产者
void producer(){
for(int i = 1; i <= 5; ++i){
lock_guard<mutex> lock(mtx);
data_queue.push(i);
cout << "生产者生产数据:" << i << endl;
}
is_produced = true;
cv.notify_all();
}
// 消费者(条件变量方式)
void consumer(){
unique_lock<mutex> lock(mtx);
// 等待条件:队列不为空 或 生产已完成
cv.wait(lock, [](){ return !data_queue.empty() || is_produced; });
while(!data_queue.empty()){
int data = data_queue.front();
data_queue.pop();
cout << "消费者消费数据:" << data << endl;
}
}
int main(){
thread t_producer(producer);
thread t_consumer(consumer);
t_producer.join();
t_consumer.join();
return 0;
}
运行该程序,消费者线程在没有数据时会进入等待状态,不会浪费 CPU 资源。生产者生产完成后唤醒消费者,消费者再进行数据消费。
虚假唤醒指的是线程在没有被 notify_one()/notify_all() 唤醒的情况下,也可能从 wait() 中返回。为了避免这种情况,我们必须使用带条件的 wait() 重载版本,通过判断条件是否满足来决定是否继续执行。
例如,在消费者线程中,我们用 cv.wait(lock, [](){ return !data_queue.empty() || is_produced; }) 替代无参的 wait(),确保只有在队列有数据或生产完成时,线程才会被唤醒并继续执行。
我们实现一个支持多个生产者和多个消费者的模型,使用条件变量保证线程间的同步协作:
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <vector>
using namespace std;
const int MAX_QUEUE_SIZE = 5;
queue<int> data_queue;
mutex mtx;
condition_variable cv_producer;
condition_variable cv_consumer;
bool stop_flag = false;
// 生产者函数
void producer_func(int id){
for(int i = 1; i <= 3; ++i){
unique_lock<mutex> lock(mtx);
// 等待队列有空位
cv_producer.wait(lock, [](){ return data_queue.size() < MAX_QUEUE_SIZE || stop_flag; });
if(stop_flag) break;
int data = id * 10 + i;
data_queue.push(data);
cout << "生产者" << id << "生产数据:" << data << ",队列大小:" << data_queue.size() << endl;
cv_consumer.notify_one();
}
}
// 消费者函数
void consumer_func(int id){
while(true){
unique_lock<mutex> lock(mtx);
// 等待队列有数据
cv_consumer.wait(lock, [](){ return !data_queue.empty() || stop_flag; });
if(stop_flag && data_queue.empty()) break;
int data = data_queue.front();
data_queue.pop();
cout << "消费者" << id << "消费数据:" << data << ",队列大小:" << data_queue.size() << endl;
cv_producer.notify_one();
}
}
int main(){
vector<thread> producers;
vector<thread> consumers;
for(int i = 1; i <= 2; ++i){
producers.emplace_back(producer_func, i);
}
for(int i = 1; i <= 3; ++i){
consumers.emplace_back(consumer_func, i);
}
for(auto& t : producers){
t.join();
}
stop_flag = true;
cv_consumer.notify_all();
for(auto& t : consumers){
t.join();
}
cout << "所有生产和消费任务完成" << endl;
return 0;
}
✅ 运行效果:
wait() 函数需要先获取互斥锁,才能保证条件判断的线程安全。wait():可以有效避免虚假唤醒,确保线程在正确的条件下被唤醒。notify_one() 和 notify_all() 的选择:
notify_one(),效率更高。notify_all(),比如生产完成后通知所有消费者。std::condition_variable 必须与 std::unique_lock 配合使用,核心接口是 wait()、notify_one() 和 notify_all()。wait() 重载版本可以解决虚假唤醒问题,是实际开发中的首选。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online