C++ 多线程同步之条件变量(condition_variable)实战

发布时间:2026/7/17 9:00:40

C++ 多线程同步之条件变量(condition_variable)实战 C 多线程同步之条件变量condition_variable实战学习目标掌握 C 标准库中条件变量的使用方法理解条件变量与互斥锁的协同工作机制能够解决多线程间的等待-通知问题。学习重点std::condition_variable的核心接口、wait()与notify_one()/notify_all()的配合使用、生产者-消费者模型的实现。49.1 条件变量的引入场景在多线程编程中我们经常会遇到线程需要等待某个条件满足后再执行的场景。比如生产者线程生产数据后消费者线程才能消费队列不为空时消费者才能从中取数据。如果仅用互斥锁实现消费者线程只能不断轮询检查条件这会造成 CPU 资源的浪费。⚠️注意事项单纯的轮询会导致 CPU 空转降低程序运行效率条件变量就是为解决这类问题而生的。举个简单的轮询反例消费者不断检查队列是否有数据#includeiostream#includethread#includemutex#includequeueusingnamespacestd;queueintdata_queue;mutex mtx;// 生产者voidproducer(){for(inti1;i5;i){lock_guardmutexlock(mtx);data_queue.push(i);cout生产者生产数据iendl;}}// 消费者轮询方式voidconsumer(){while(true){lock_guardmutexlock(mtx);if(!data_queue.empty()){intdatadata_queue.front();data_queue.pop();cout消费者消费数据dataendl;if(data5)break;}// 没有数据时依然会不断循环检查浪费CPU}}intmain(){threadt_producer(producer);threadt_consumer(consumer);t_producer.join();t_consumer.join();return0;}运行该程序消费者线程在队列空的时候会一直循环检查造成不必要的 CPU 开销。49.2 C 标准库中的条件变量C11 标准库在condition_variable头文件中提供了std::condition_variable类它需要与std::mutex配合使用实现线程间的高效等待与通知。49.2.1std::condition_variable的核心接口wait(unique_lock lck)调用该函数的线程会释放持有的互斥锁并进入阻塞状态。直到被其他线程的notify_one()或notify_all()唤醒。唤醒后线程会重新获取互斥锁然后继续执行。wait(unique_lock lck, Predicate pred)带条件的等待只有当pred条件为false时才会阻塞。被唤醒后会先检查条件条件满足才会继续执行否则再次阻塞。该重载可以避免虚假唤醒问题。notify_one()唤醒一个正在等待该条件变量的线程。如果有多个线程等待随机唤醒其中一个。notify_all()唤醒所有正在等待该条件变量的线程。49.2.2 搭配std::unique_lock的原因std::condition_variable的wait()函数要求传入std::unique_lock而不是std::lock_guard。这是因为wait()过程中需要临时释放锁而std::unique_lock支持手动解锁和加锁std::lock_guard仅支持构造加锁、析构解锁无法满足需求。✅核心结论条件变量必须与std::unique_lock配合使用才能实现等待时释放锁、唤醒后重新加锁的逻辑。49.3 条件变量实战解决等待-通知问题我们使用std::condition_variable改造 49.1 节的轮询反例实现高效的生产者-消费者模型#includeiostream#includethread#includemutex#includequeue#includecondition_variableusingnamespacestd;queueintdata_queue;mutex mtx;condition_variable cv;boolis_producedfalse;// 生产完成标志// 生产者voidproducer(){for(inti1;i5;i){lock_guardmutexlock(mtx);data_queue.push(i);cout生产者生产数据iendl;}is_producedtrue;cv.notify_all();// 生产完成唤醒所有等待的消费者}// 消费者条件变量方式voidconsumer(){unique_lockmutexlock(mtx);// 等待条件队列不为空 或 生产已完成cv.wait(lock,[](){return!data_queue.empty()||is_produced;});while(!data_queue.empty()){intdatadata_queue.front();data_queue.pop();cout消费者消费数据dataendl;}}intmain(){threadt_producer(producer);threadt_consumer(consumer);t_producer.join();t_consumer.join();return0;}运行该程序消费者线程在没有数据时会进入等待状态不会浪费 CPU 资源。生产者生产完成后唤醒消费者消费者再进行数据消费。49.3.1 解决虚假唤醒问题虚假唤醒指的是线程在没有被notify_one()/notify_all()唤醒的情况下也可能从wait()中返回。为了避免这种情况我们必须使用带条件的wait()重载版本通过判断条件是否满足来决定是否继续执行。例如在消费者线程中我们用cv.wait(lock, [](){ return !data_queue.empty() || is_produced; })替代无参的wait()确保只有在队列有数据或生产完成时线程才会被唤醒并继续执行。49.4 实战案例多生产者-多消费者模型我们实现一个支持多个生产者和多个消费者的模型使用条件变量保证线程间的同步协作#includeiostream#includethread#includemutex#includequeue#includecondition_variable#includevectorusingnamespacestd;constintMAX_QUEUE_SIZE5;// 队列最大容量queueintdata_queue;mutex mtx;condition_variable cv_producer;// 生产者条件变量condition_variable cv_consumer;// 消费者条件变量boolstop_flagfalse;// 停止标志// 生产者函数voidproducer_func(intid){for(inti1;i3;i){unique_lockmutexlock(mtx);// 等待队列有空位cv_producer.wait(lock,[](){returndata_queue.size()MAX_QUEUE_SIZE||stop_flag;});if(stop_flag)break;intdataid*10i;data_queue.push(data);cout生产者id生产数据data队列大小data_queue.size()endl;cv_consumer.notify_one();// 唤醒一个消费者}}// 消费者函数voidconsumer_func(intid){while(true){unique_lockmutexlock(mtx);// 等待队列有数据cv_consumer.wait(lock,[](){return!data_queue.empty()||stop_flag;});if(stop_flagdata_queue.empty())break;intdatadata_queue.front();data_queue.pop();cout消费者id消费数据data队列大小data_queue.size()endl;cv_producer.notify_one();// 唤醒一个生产者}}intmain(){// 创建 2 个生产者线程和 3 个消费者线程vectorthreadproducers;vectorthreadconsumers;for(inti1;i2;i){producers.emplace_back(producer_func,i);}for(inti1;i3;i){consumers.emplace_back(consumer_func,i);}// 等待所有生产者完成for(autot:producers){t.join();}// 设置停止标志唤醒所有消费者stop_flagtrue;cv_consumer.notify_all();// 等待所有消费者完成for(autot:consumers){t.join();}cout所有生产和消费任务完成endl;return0;}✅运行效果生产者线程会在队列满时等待队列有空位时继续生产。消费者线程会在队列空时等待队列有数据时继续消费。生产完成后设置停止标志唤醒所有消费者线程并退出避免线程阻塞。49.5 条件变量与互斥锁的协同要点条件变量必须搭配互斥锁使用wait()函数需要先获取互斥锁才能保证条件判断的线程安全。优先使用带条件的wait()可以有效避免虚假唤醒确保线程在正确的条件下被唤醒。notify_one()和notify_all()的选择当只需要唤醒一个等待线程时使用notify_one()效率更高。当需要唤醒所有等待线程时使用notify_all()比如生产完成后通知所有消费者。49.6 本章小结条件变量用于解决多线程间的等待-通知问题避免了轮询造成的 CPU 资源浪费。std::condition_variable必须与std::unique_lock配合使用核心接口是wait()、notify_one()和notify_all()。带条件的wait()重载版本可以解决虚假唤醒问题是实际开发中的首选。生产者-消费者模型是条件变量的典型应用场景通过合理设计条件可以实现高效的线程协作。

相关新闻