condition_variable不能单独用作信号量,因其无内部计数器,notify调用无人等待时会丢失,必须搭配mutex保护的显式状态变量并用while循环检查条件。

为什么 condition_variable 不能单独用作信号量
std::condition_variable 本身不是信号量,它没有内部计数器,也不保存“唤醒次数”。只调用 notify_one() 或 notify_all() 而无人在 wait() 中阻塞时,通知会直接丢失。这和 sem_wait()/sem_post() 的行为本质不同——后者是可积累的。
常见错误现象:notify_one() 提前触发,后续线程调用 wait() 却永远卡住;或多个 notify_* 连续发,但只唤醒一个线程,其余通知被吞掉。
- 必须搭配一个显式的、线程安全的状态变量(如
bool ready或int count)使用 - 状态检查必须放在
while循环里,不能用if——防止虚假唤醒 - 所有对共享状态的读写,必须在同一个
std::mutex保护下完成
如何用 condition_variable 模拟二值信号量(0/1)
核心思路:用 bool signaled 表示是否已被触发,配合 std::mutex 和 std::condition_variable 构建“等待-通知”闭环。
class binary_semaphore {
std::mutex mtx_;
std::condition_variable cv_;
bool signaled_ = false;
public:
void notify() {
std::lock_guard<std::mutex> lk(mtx_);
signaled_ = true;
cv_.notify_one(); // 只需唤醒一个
}
void wait() {
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this] { return signaled_; }); // while 循环 + lambda 检查
signaled_ = false; // 消费后重置
}
};
注意点:
立即学习“C++免费学习笔记(深入)”;
-
wait()返回时,signaled_一定为true,但立刻被设回false,保证“一次消费”语义 - 如果想支持超时等待,改用
cv_.wait_for(lk, dur, [...] {...}) - 不加锁直接读
signaled_是未定义行为;不加锁调用notify()也可能导致wait()永远错过信号
如何模拟计数信号量(支持多次 post / wait)
把 bool 换成 int 计数器即可,但要注意溢出和负值保护。
class counting_semaphore {
std::mutex mtx_;
std::condition_variable cv_;
int count_ = 0;
public:
explicit counting_semaphore(int initial = 0) : count_(initial) {}
void signal() {
std::lock_guard<std::mutex> lk(mtx_);
++count_;
cv_.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this] { return count_ > 0; });
--count_;
}
};
关键差异:
-
signal()不再清零count_,而是递增;wait()成功后才递减 - 初始值可以为负,但
wait()会一直阻塞直到有足够signal()抵消 - 若需线程安全的
try_wait(),需用cv_.wait_for(...)配合std::chrono::seconds(0),并检查返回值
实际异步通知中容易漏掉的细节
真实场景下,比如主线程启动 worker 线程后等待其初始化完成,或 IO 完成后通知回调,光靠 condition_variable 还不够稳健。
- worker 线程可能在
notify()前崩溃,导致调用wait()的线程永久挂起 —— 必须配超时机制,例如wait_for(lk, 5s)并检查返回值 - 多个线程同时
wait(),但只有一个该被唤醒?用notify_one();全部都要响应?用notify_all(),但注意性能开销 - 不要在析构函数里调用
wait(),也不要在持有锁期间做耗时操作(比如 IO、内存分配),否则阻塞其他线程 -
std::condition_variable要求wait()的std::unique_lock必须关联同一 mutex;混用std::shared_mutex或其它锁类型会编译失败或 UB
最常被忽略的一点:condition_variable 本身不保证原子性,所有同步逻辑都压在开发者对共享变量的读写顺序和锁粒度控制上。写错一行状态检查条件,就可能引入竞态或死锁。


















