C++ 条件变量
C++条件变量[编辑 | 编辑源代码]
条件变量(Condition Variable)是C++多线程编程中用于线程间同步的重要机制,它允许线程在特定条件满足前挂起等待,并在条件可能满足时被唤醒。条件变量通常与互斥锁(mutex)配合使用,是生产者-消费者模型、线程池等并发模式的核心组件。
基本概念[编辑 | 编辑源代码]
条件变量解决了线程间通信的"等待-通知"问题。其核心思想是:
- 线程A检查条件,若条件不满足则等待(原子地释放锁并进入休眠)
- 线程B修改条件后通知等待线程
- 线程A被唤醒后重新获取锁并验证条件
C++标准库提供std::condition_variable
(需#include <condition_variable>
)和std::condition_variable_any
两种实现。
核心方法[编辑 | 编辑源代码]
方法 | 描述 |
---|---|
notify_one() |
唤醒一个等待线程 |
notify_all() |
唤醒所有等待线程 |
wait(lock) |
原子地解锁并等待,被唤醒后重新获取锁 |
wait(lock, predicate) |
带条件的等待(推荐用法) |
wait_for() |
带超时的等待 |
wait_until() |
带绝对时间点的等待 |
基本用法示例[编辑 | 编辑源代码]
以下展示一个简单的生产者-消费者模型:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
void producer() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_one(); // 通知一个消费者
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !data_queue.empty(); }); // 等待队列非空
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumed: " << data << std::endl;
lock.unlock();
if (data == 4) break; // 结束条件
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
输出示例:
Produced: 0 Consumed: 0 Produced: 1 Consumed: 1 ... Produced: 4 Consumed: 4
虚假唤醒与条件检查[编辑 | 编辑源代码]
条件变量可能因系统原因出现虚假唤醒(spurious wakeup),因此必须: 1. 总是使用带有谓词(predicate)的wait版本 2. 或者在循环中检查条件
正确做法对比:
// 错误:可能虚假唤醒
cv.wait(lock);
// 正确:使用谓词
cv.wait(lock, []{ return ready; });
// 正确:循环检查
while (!ready) {
cv.wait(lock);
}
线程同步状态图[编辑 | 编辑源代码]
高级主题[编辑 | 编辑源代码]
1. 条件变量与锁的选择[编辑 | 编辑源代码]
std::condition_variable
只能与std::unique_lock<std::mutex>
配合使用std::condition_variable_any
可与任何满足BasicLockable要求的锁配合
2. 性能考虑[编辑 | 编辑源代码]
- 通知操作(notify)通常不持有锁以获得更好性能
- 批量处理时可优先使用
notify_all()
3. 与future/promise的区别[编辑 | 编辑源代码]
条件变量适用于多对多线程通信,而future/promise适用于一次性事件通知。
实际应用案例[编辑 | 编辑源代码]
案例:有限资源池 实现一个限制最大并发数的线程池:
class ThreadPool {
std::mutex mtx;
std::condition_variable cv;
int active_threads = 0;
const int max_threads;
public:
ThreadPool(int max) : max_threads(max) {}
void execute(std::function<void()> task) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]{ return active_threads < max_threads; });
active_threads++;
lock.unlock();
std::thread([this, task]{
task();
std::lock_guard<std::mutex> lock(mtx);
active_threads--;
cv.notify_one();
}).detach();
}
};
常见问题[编辑 | 编辑源代码]
Q: 为什么条件变量需要互斥锁? A: 因为条件的检查与修改必须是原子操作,防止竞态条件。
Q: notify_one()和notify_all()如何选择? A: 当只有一个等待线程能满足条件时用notify_one(),多个可能满足时用notify_all()。
Q: 条件变量会自己记住通知吗? A: 不会。如果在没有线程等待时调用notify,通知会被丢弃(与信号量不同)。
数学原理[编辑 | 编辑源代码]
条件变量的行为可以用管程(Monitor)理论描述:
最佳实践[编辑 | 编辑源代码]
1. 总是使用RAII管理锁(如lock_guard
、unique_lock
)
2. 通知前尽量释放锁
3. 优先使用带谓词的wait版本
4. 避免在持有锁时执行耗时操作
5. 考虑使用std::atomic
简化简单场景