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

学习目标:掌握 C++ 标准库中条件变量的使用方法,理解条件变量与互斥锁的协同工作机制,能够解决多线程间的等待 - 通知问题。
学习重点:std::condition_variable 的核心接口、wait() 与 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