C++ 条件变量
目录
- #1-stdcondition_variable-核心函数详解
- #2-stdcondition_variable_any-核心函数详解
- #3-等待函数详解
- #4-通知函数详解
- #5-条件变量的正确使用模式
- #6-常见陷阱与最佳实践
0. 条件变量基本概念
头文件
cpp
#include <condition_variable>
#include <mutex>
#include <thread>
主要类型
cpp
std::condition_variable; // 基本条件变量
std::condition_variable_any; // 可与任何锁类型配合的条件变量
1. std::condition_variable 核心函数详解
1.1 构造函数和析构函数
默认构造函数
cpp
constexpr condition_variable() noexcept;
作用:构造一个条件变量对象。
使用示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::condition_variable cv; // 默认构造
std::mutex mtx;
bool ready = false;
void worker_thread() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件满足
std::cout << "Worker thread awakened!" << std::endl;
}
int main() {
std::thread worker(worker_thread);
// 主线程准备工作
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知等待的线程
worker.join();
return 0;
}
注意事项:
- 条件变量不可复制、不可移动
- 析构时不能有线程正在等待,否则行为未定义
1.2 赋值运算符(被删除)
cpp
condition_variable& operator=(const condition_variable&) = delete;
作用:条件变量不可赋值。
易错点:
cpp
std::condition_variable cv1, cv2;
// cv1 = cv2; // 错误!条件变量不可赋值
2. std::condition_variable_any 核心函数详解
与任意互斥类型配合的条件变量
特点:可以与任何满足基本互斥要求的锁类型配合使用。
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <condition_variable>
std::condition_variable_any cv_any;
std::shared_timed_mutex rw_mtx; // 读写锁
bool data_ready = false;
void reader_thread() {
std::shared_lock<std::shared_timed_mutex> lock(rw_mtx);
cv_any.wait(lock, []{ return data_ready; });
std::cout << "Reader: data is ready!" << std::endl;
}
void writer_thread() {
std::unique_lock<std::shared_timed_mutex> lock(rw_mtx);
data_ready = true;
cv_any.notify_all();
}
int main() {
std::thread reader(reader_thread);
std::thread writer(writer_thread);
reader.join();
writer.join();
return 0;
}
使用场景:
- 需要与非常规互斥类型配合时
- 需要更灵活的锁管理时
性能注意 :通常比 std::condition_variable 性能稍差。
3. 等待函数详解
3.1 wait() 函数 - 基础版本
函数签名
cpp
void wait(std::unique_lock<std::mutex>& lock);
作用:使当前线程进入等待状态,直到被其他线程通知。在等待时会自动释放锁,被唤醒后会重新获取锁。
参数:
lock:必须是已锁定的std::unique_lock<std::mutex>对象
返回值:void
使用示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool condition_met = false;
void waiting_thread() {
std::unique_lock<std::mutex> lock(mtx);
std::cout << "Waiting thread: entering wait" << std::endl;
cv.wait(lock); // 释放锁并等待
std::cout << "Waiting thread: awakened, condition: " << condition_met << std::endl;
}
void notifying_thread() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
condition_met = true;
}
cv.notify_one();
std::cout << "Notifying thread: notified waiting thread" << std::endl;
}
int main() {
std::thread t1(waiting_thread);
std::thread t2(notifying_thread);
t1.join();
t2.join();
return 0;
}
关键细节:
- 原子性操作 :
wait()调用是原子的 - 先释放锁,然后阻塞线程 - 虚假唤醒:线程可能在没有通知的情况下被唤醒,必须重新检查条件
- 锁的状态:进入wait时锁必须被当前线程持有,wait返回时锁会被重新获取
3.2 wait() 函数 - 带谓词版本(推荐使用)
函数签名
cpp
template<typename Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
作用:等待直到谓词条件为true。自动处理虚假唤醒问题。
参数:
lock:已锁定的std::unique_lock<std::mutex>pred:可调用对象,返回bool,表示等待条件
返回值:void
内部等价实现:
cpp
while (!pred()) {
wait(lock);
}
使用示例:
cpp
std::condition_variable cv;
std::mutex mtx;
std::queue<int> data_queue;
bool shutdown = false;
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
// 正确用法:使用谓词避免虚假唤醒和条件检查
cv.wait(lock, []{
return !data_queue.empty() || shutdown;
});
if (!data_queue.empty()) {
int data = data_queue.front();
data_queue.pop();
std::cout << "Processing data: " << data << std::endl;
}
}
void producer() {
for (int i = 0; i < 5; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
}
cv.notify_one();
}
}
优势:
- 自动处理虚假唤醒
- 代码更简洁
- 条件检查与等待原子进行
3.3 wait_for() 函数 - 超时等待
函数签名
cpp
template<typename Rep, typename Period>
std::cv_status wait_for(std::unique_lock<std::mutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time);
template<typename Rep, typename Period, typename Predicate>
bool wait_for(std::unique_lock<std::mutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time,
Predicate pred);
作用:等待特定时间段,超时后返回。
参数:
lock:已锁定的unique_lockrel_time:等待的超时时间pred:等待条件谓词(可选)
返回值:
- 无谓词版本:
std::cv_status::timeout(超时)或std::cv_status::no_timeout(被通知) - 有谓词版本:谓词的最终结果
使用示例:
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <condition_variable>
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void timed_waiter() {
std::unique_lock<std::mutex> lock(mtx);
// 版本1:使用返回值判断
auto status = cv.wait_for(lock, std::chrono::seconds(2));
if (status == std::cv_status::timeout) {
std::cout << "Wait timed out!" << std::endl;
} else {
std::cout << "Awakened by notification" << std::endl;
}
// 版本2:使用谓词(推荐)
bool success = cv.wait_for(lock, std::chrono::seconds(3), []{ return ready; });
if (success) {
std::cout << "Condition became true within timeout" << std::endl;
} else {
std::cout << "Timeout before condition became true" << std::endl;
}
}
int main() {
std::thread waiter(timed_waiter);
// 3秒后设置ready为true,此时第二个wait会成功
std::this_thread::sleep_for(std::chrono::seconds(3));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one();
waiter.join();
return 0;
}
3.4 wait_until() 函数 - 绝对时间等待
函数签名
cpp
template<typename Clock, typename Duration>
std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
const std::chrono::time_point<Clock, Duration>& abs_time);
template<typename Clock, typename Duration, typename Predicate>
bool wait_until(std::unique_lock<std::mutex>& lock,
const std::chrono::time_point<Clock, Duration>& abs_time,
Predicate pred);
作用:等待直到指定的绝对时间点。
使用示例:
cpp
void absolute_timed_waiter() {
std::unique_lock<std::mutex> lock(mtx);
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(5);
// 等待直到特定时间点
bool success = cv.wait_until(lock, timeout, []{ return ready; });
if (success) {
std::cout << "Condition met before absolute timeout" << std::endl;
} else {
std::cout << "Absolute timeout reached" << std::endl;
}
}
使用场景:
- 需要精确的绝对超时时间
- 周期性任务调度
4. 通知函数详解
4.1 notify_one() 函数
函数签名
cpp
void notify_one() noexcept;
作用:唤醒一个等待中的线程(如果有多个线程在等待,随机选择一个)。
使用示例:
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <condition_variable>
std::condition_variable cv;
std::mutex mtx;
int data_ready = 0;
void worker(int id) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_ready > 0; });
std::cout << "Worker " << id << " awakened, data_ready: " << data_ready << std::endl;
--data_ready;
}
int main() {
std::vector<std::thread> workers;
// 创建5个工作线程
for (int i = 0; i < 5; ++i) {
workers.emplace_back(worker, i);
}
std::this_thread::sleep_for(std::chrono::seconds(1));
// 逐个通知工作线程
for (int i = 0; i < 3; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
++data_ready;
}
cv.notify_one(); // 每次只唤醒一个线程
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 通知剩余线程
{
std::lock_guard<std::mutex> lock(mtx);
data_ready += 2; // 满足剩余两个线程
}
cv.notify_all(); // 唤醒所有剩余线程
for (auto& t : workers) {
t.join();
}
return 0;
}
特点:
- 轻量级操作
- 不保证唤醒顺序
- 如果没有等待线程,通知会被忽略
4.2 notify_all() 函数
函数签名
cpp
void notify_all() noexcept;
作用:唤醒所有等待中的线程。
使用示例:
cpp
void broadcast_example() {
std::vector<std::thread> threads;
std::condition_variable cv;
std::mutex mtx;
bool start = false;
for (int i = 0; i < 3; ++i) {
threads.emplace_back([i, &cv, &mtx, &start] {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&start] { return start; });
std::cout << "Thread " << i << " started!" << std::endl;
});
}
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
start = true;
}
cv.notify_all(); // 唤醒所有3个线程
for (auto& t : threads) {
t.join();
}
}
使用场景:
- 多个线程等待同一条件
- 资源可用性通知
- 系统状态改变通知
5. 条件变量的正确使用模式
5.1 生产者-消费者模式(有界缓冲区)
cpp
#include <queue>
#include <thread>
#include <iostream>
#include <condition_variable>
template<typename T>
class BoundedBuffer {
private:
std::queue<T> buffer_;
size_t capacity_;
std::mutex mtx_;
std::condition_variable not_full_; // 缓冲区不满的条件
std::condition_variable not_empty_; // 缓冲区不空的条件
public:
BoundedBuffer(size_t capacity) : capacity_(capacity) {}
void put(T item) {
std::unique_lock<std::mutex> lock(mtx_);
// 等待缓冲区不满
not_full_.wait(lock, [this] { return buffer_.size() < capacity_; });
buffer_.push(std::move(item));
std::cout << "Produced, buffer size: " << buffer_.size() << std::endl;
lock.unlock();
not_empty_.notify_one(); // 通知消费者
}
T get() {
std::unique_lock<std::mutex> lock(mtx_);
// 等待缓冲区不空
not_empty_.wait(lock, [this] { return !buffer_.empty(); });
T item = std::move(buffer_.front());
buffer_.pop();
std::cout << "Consumed, buffer size: " << buffer_.size() << std::endl;
lock.unlock();
not_full_.notify_one(); // 通知生产者
return item;
}
};
void producer(BoundedBuffer<int>& buffer, int id) {
for (int i = 0; i < 5; ++i) {
buffer.put(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer(BoundedBuffer<int>& buffer, int id) {
for (int i = 0; i < 5; ++i) {
int item = buffer.get();
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
}
5.2 读写锁模式
cpp
#include <shared_mutex>
#include <condition_variable>
class ReadWriteLock {
private:
std::mutex mtx_;
std::condition_variable readers_cv_;
std::condition_variable writers_cv_;
int readers_ = 0;
int writers_ = 0;
int waiting_writers_ = 0;
bool write_in_progress_ = false;
public:
void read_lock() {
std::unique_lock<std::mutex> lock(mtx_);
readers_cv_.wait(lock, [this] {
return !write_in_progress_ && waiting_writers_ == 0;
});
++readers_;
}
void read_unlock() {
std::lock_guard<std::mutex> lock(mtx_);
if (--readers_ == 0) {
writers_cv_.notify_one();
}
}
void write_lock() {
std::unique_lock<std::mutex> lock(mtx_);
++waiting_writers_;
writers_cv_.wait(lock, [this] {
return readers_ == 0 && !write_in_progress_;
});
--waiting_writers_;
write_in_progress_ = true;
}
void write_unlock() {
std::lock_guard<std::mutex> lock(mtx_);
write_in_progress_ = false;
if (waiting_writers_ > 0) {
writers_cv_.notify_one();
} else {
readers_cv_.notify_all();
}
}
};
6. 常见陷阱与最佳实践
6.1 丢失唤醒问题
错误示例:
cpp
// 错误:条件检查和等待之间存在竞争
void consumer_bad() {
std::unique_lock<std::mutex> lock(mtx);
if (queue.empty()) { // 检查条件
// 在这里可能发生上下文切换,生产者可能已经添加了数据并调用了notify
cv.wait(lock); // 然后永久等待
}
// 处理数据
}
正确做法:始终使用带谓词的wait
cpp
void consumer_good() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !queue.empty(); }); // 原子性检查+等待
// 处理数据
}
6.2 虚假唤醒处理
必须使用循环或谓词:
cpp
// 方式1:手动循环(不推荐,容易出错)
void consumer_manual() {
std::unique_lock<std::mutex> lock(mtx);
while (queue.empty()) { // 必须用while,不能用if
cv.wait(lock);
}
// 处理数据
}
// 方式2:使用谓词(推荐)
void consumer_predicate() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !queue.empty(); });
// 处理数据
}
6.3 通知时机的选择
在锁内通知(可能降低性能):
cpp
void producer_inside_lock() {
std::lock_guard<std::mutex> lock(mtx);
queue.push(data);
cv.notify_one(); // 在锁内通知
}
在锁外通知(推荐,性能更好):
cpp
void producer_outside_lock() {
{
std::lock_guard<std::mutex> lock(mtx);
queue.push(data);
} // 锁在这里释放
cv.notify_one(); // 在锁外通知
}
6.4 条件变量与谓词状态的生命周期
错误示例:
cpp
// 错误:谓词捕获了局部变量的引用
void problematic_consumer() {
std::unique_lock<std::mutex> lock(mtx);
bool local_condition = false; // 局部变量
// 危险:lambda捕获了局部变量的引用
cv.wait(lock, [&local_condition] { return local_condition; });
}
正确做法:确保谓词中使用的变量生命周期足够长
cpp
class ThreadSafeContainer {
private:
std::mutex mtx_;
std::condition_variable cv_;
bool shutdown_ = false; // 成员变量,生命周期与容器相同
std::queue<int> queue_;
public:
void consumer() {
std::unique_lock<std::mutex> lock(mtx_);
// 安全:捕获成员变量
cv_.wait(lock, [this] { return !queue_.empty() || shutdown_; });
if (!queue_.empty()) {
// 处理数据
}
}
};
6.5 条件变量的析构问题
重要规则:在析构条件变量时,不能有线程正在等待它。
cpp
class SafeDestructorExample {
private:
std::mutex mtx_;
std::condition_variable cv_;
bool shutdown_ = false;
std::thread worker_;
public:
SafeDestructorExample() : worker_([this] { work(); }) {}
~SafeDestructorExample() {
{
std::lock_guard<std::mutex> lock(mtx_);
shutdown_ = true;
}
cv_.notify_all(); // 唤醒所有等待线程
worker_.join(); // 等待工作线程结束
// 现在可以安全析构condition_variable
}
private:
void work() {
std::unique_lock<std::mutex> lock(mtx_);
cv_.wait(lock, [this] { return shutdown_; });
}
};
完整的最佳实践示例
cpp
#include <iostream>
#include <thread>
#include <queue>
#include <atomic>
#include <condition_variable>
class ThreadSafeQueue {
private:
mutable std::mutex mtx_;
std::condition_variable cv_;
std::queue<int> queue_;
std::atomic<bool> shutdown_{false};
public:
// 线程安全的推入操作
bool push(int value) {
if (shutdown_) return false;
{
std::lock_guard<std::mutex> lock(mtx_);
queue_.push(value);
}
cv_.notify_one(); // 在锁外通知,性能更好
return true;
}
// 线程安全的弹出操作(阻塞)
bool pop(int& value) {
std::unique_lock<std::mutex> lock(mtx_);
// 使用谓词避免虚假唤醒和竞争条件
cv_.wait(lock, [this] {
return !queue_.empty() || shutdown_;
});
if (queue_.empty()) {
return false; // 关闭信号
}
value = queue_.front();
queue_.pop();
return true;
}
// 线程安全的弹出操作(带超时)
bool pop_timeout(int& value, std::chrono::milliseconds timeout) {
std::unique_lock<std::mutex> lock(mtx_);
bool success = cv_.wait_for(lock, timeout, [this] {
return !queue_.empty() || shutdown_;
});
if (!success || queue_.empty()) {
return false;
}
value = queue_.front();
queue_.pop();
return true;
}
// 安全关闭
void shutdown() {
shutdown_ = true;
cv_.notify_all(); // 唤醒所有等待线程
}
// 检查是否已关闭
bool is_shutdown() const {
return shutdown_;
}
};
这份详细的C++条件变量教程涵盖了所有核心函数的用法、注意事项和最佳实践,包括正确的使用模式、常见陷阱的避免方法以及性能优化技巧。