C++ 条件变量的用法
在 C++ 中,条件变量(std::condition_variable
)用于线程间的同步,通常搭配 std::mutex
和 std::unique_lock
以实现线程的等待和通知机制。主要的用途包括生产者-消费者模型、线程同步等。
基本用法
#include <iostream>
#include <condition_variable>
#include <thread>
#include <mutex>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void consumer()
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] () { return ready; });
// or: cv.wait(lock, [] { return ready; });
std::cout << "Consumer Thread start running" << std::endl;
}
void worker()
{
std::this_thread::sleep_for(std::chrono::seconds(2)); //模拟生产耗时操作
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 唤醒一个等待的线程
}
int main()
{
std::thread t0(consumer);
std::thread t1(worker);
t0.join();
t1.join();
return 0;
}
代码详解:
void consumer()
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
std::cout << "Consumer Thread start running" << std::endl;
}
// 1. 进入consumer,当前线程加锁mtx,其他线程不能修改ready;
// 2. 执行cv.wait(lock, predicate) 语句;
// a. 若lambda表达式 [] {return ready} 返回 true,则cv.wait()不会进入等待, 直接返回,consumer线程继续执行后续语句;函数退回后,释放mtx锁;
// b. 若lambda表达式 [] {return ready} 返回 false,cv.wait()释放mtx锁,并进入等待(阻塞)状态,其他线程可以更新ready状态,并调用cv.notify_one()或cv.notify_all()后,唤醒consumer线程,consumer重新获取mtx锁,再次重复执行步骤2;
// cv.wait(lock, predicate)
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] () { return ready; });
// 等价于:
std::unique_lock<std::mutex> lock(mtx);
while (!ready) { // 等待条件变量
cv.wait(lock); // 释放锁,并等待 notify
}
notify_one() 和 notify_all()
当有多个线程等待条件变量时,notify_one()
只能唤醒一个线程,而 notify_all()
会唤醒所有线程。
#include <iostream>
#include <vector>
#include <condition_variable>
#include <thread>
#include <mutex>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void consumer(int id)
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] () { return ready; });
std::cout << "Consumer Thread id[" << id << "] start running" << std::endl;
}
void worker()
{
std::this_thread::sleep_for(std::chrono::seconds(2)); //模拟生产耗时操作
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
// cv.notify_one(); // 唤醒一个等待的线程
cv.notify_all(); // 唤醒所以等待的线程
}
int main()
{
std::vector<std::thread> consumers;
for (int i = 0; i < 3; i++)
{
consumers.emplace_back(consumer, i); //启动多个消费者线程
}
std::thread t1(worker); // 启动生产者线程
for (auto &consumer_thread : consumers)
{
consumer_thread.join();
}
t1.join();
return 0;
}
解释:
consumer
线程等待cv.wait(lock, [] { return ready; })
,被notify_all()
唤醒后继续执行。notify_all()
适用于广播通知多个线程。
生产者-消费者模型
条件变量常用于生产者-消费者模型,确保生产者和消费者协同工作。
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
std::queue<int> dataQueue;
std::mutex mtx;
std::condition_variable cv;
bool finished = false;
void producer() {
for (int i = 1; i <= 5; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
dataQueue.push(i);
std::cout << "Produced: " << i << std::endl;
}
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
{
std::lock_guard<std::mutex> lock(mtx);
finished = true;
}
cv.notify_all();
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !dataQueue.empty() || finished; });
while (!dataQueue.empty()) {
int data = dataQueue.front();
dataQueue.pop();
std::cout << "Consumed: " << data << std::endl;
}
if (finished) break;
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
解释:
- 生产者不断向
dataQueue
添加数据,并notify_one()
唤醒消费者。 - 消费者等待
cv.wait()
直到队列非空,消费数据后继续等待。 - 当
finished = true
时,消费者退出。
条件变量适用于任务同步、线程间通讯,尤其适用于生产者-消费者模型等并发场景。
问题:
1. 多个消费者线程wait时,notify_one() 是随机唤醒一个线程,还是第一个进入等待的线程;
在 C++ 的 std::condition_variable
中,notify_one()
唤醒的线程是随机的,并不是严格按照等待顺序(FIFO)唤醒的。具体而言:
notify_one()
的行为
当多个线程在 cv.wait(lock, predicate)
处阻塞时,调用 cv.notify_one()
随机唤醒其中一个线程,具体行为取决于操作系统的线程调度策略和 C++ 标准库的实现:
- 可能唤醒最早等待的线程(FIFO 方式),但没有保证。
- 也可能唤醒最近进入等待的线程(LIFO 方式,取决于底层实现)。
- 线程调度器可能决定选择哪个线程,导致不确定性。
🔹 结论:notify_one()
不会严格按照进入 wait()
的顺序唤醒线程,而是随机选择一个线程。
2. std::lock_guard 和 std::unique_lock 两种锁
特性 | std::lock_guard | std::unique_lock |
---|---|---|
是否默认加锁 | ✅ 是 | ✅ 是 |
是否默认解锁(作用域外 / 对象被析构) | ✅ 是 | ✅ 是 |
是否可手动 unlock() | ❌ 不能手动解锁 | ✅ 可 unlock() |
是否可手动 lock() | ❌ 不能手动加锁 | ✅ 可 lock() |
是否可用于 condition_variable | ❌ 不能 | ✅ 适用 |
是否可移动 | ❌ 不能移动 | ✅ 可移动 |
是否更轻量 | ✅ 轻量级 | ❌ 略有开销 |
为什么作用域结束会自动释放锁?这是因为:
std::lock_guard
和std::unique_lock
是 RAII(资源获取即初始化)机制的对象。- 当它们的生命周期结束时,析构函数会自动调用
mutex.unlock()
,确保锁被释放。
✅ 推荐使用
- 短临界区 →
std::lock_guard
- 需要解锁 / 重新加锁 /
condition_variable
→std::unique_lock
🔹 简单来说
std::lock_guard
是更快、更轻量级的锁std::unique_lock
是更灵活、更强大的锁