- 操作系统:ubuntu22.04
- IDE:Visual Studio Code
- 编程语言:C++11
条件变量(Condition Variable) 是实现线程间高效等待与通知机制的核心工具,通常与互斥锁配合使用,用于解决"生产者-消费者"、"任务队列"、"线程协调"等经典并发问题。
一、为什么需要条件变量?
❌ 问题:忙等待(Busy Waiting)效率低
            
            
              cpp
              
              
            
          
          std::mutex mtx;
bool ready = false;
// 线程A(等待者)
while (!ready)
{
    // 空循环,持续检查
}
// do work...
// 线程B(通知者)
{
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}- CPU 资源浪费:等待线程不断轮询,占用 CPU。
- 响应延迟:无法立即响应状态变化。
✅ 解决方案:条件变量
- 等待线程挂起(阻塞),不消耗 CPU。
- 通知线程唤醒等待线程。
- 高效、节能、响应及时。
二、C++11 中的条件变量类型
C++11 在 <condition_variable> 中提供了两种条件变量:
| 类型 | 说明 | 
|---|---|
| std::condition_variable | 仅支持 std::unique_lockstd::mutex,性能更高,最常用 | 
| std::condition_variable_any | 支持任意满足 BasicLockable 的锁(如 shared_mutex),但性能略低 | 
✅ 推荐优先使用 std::condition_variable。
三、基本用法:wait() 与 notify_one() / notify_all()
核心 API:
            
            
              cpp
              
              
            
          
          void wait(std::unique_lock<std::mutex>& lock);
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
void notify_one();  // 唤醒一个等待线程
void notify_all();  // 唤醒所有等待线程🌰 示例:简单线程同步
            
            
              cpp
              
              
            
          
          #include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker()
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; }); // 等待 ready == true
    std::cout << "Worker: working now!\n";
}
void starter() 
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 通知一个等待线程
}
int main() 
{
    std::thread t1(worker);
    std::thread t2(starter);
    t1.join(); t2.join();
}输出:
            
            
              bash
              
              
            
          
          Worker: working now!四、深入理解 wait() 的工作机制
cv.wait(lock, pred) 等价于:
            
            
              cpp
              
              
            
          
          while (!pred()) 
{
    cv.wait(lock); // 内部:1. unlock mutex; 2. 阻塞等待; 3. 被唤醒后重新 lock
}关键点:
- 自动释放锁:调用 wait() 时,会自动释放传入的 unique_lock,允许其他线程获取锁并修改共享状态。
- 原子性:释放锁 + 进入等待 是原子操作,避免通知丢失(lost wake-up)。
- 虚假唤醒(Spurious Wakeup):即使没有调用 notify,线程也可能被唤醒(POSIX 允许)。因此必须使用谓词(predicate) 检查条件。
✅ 所以永远不要写 cv.wait(lock); 而不带谓词!
五、经典应用:生产者-消费者模型(任务队列)
            
            
              cpp
              
              
            
          
          #include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
std::queue< int > task_queue;
std::mutex mtx;
std::condition_variable cv;
bool done = false;
// 生产者
void producer( int id )
{
    for ( int i = 0; i < 5; ++i )
    {
        {
            std::lock_guard< std::mutex > lock( mtx );
            task_queue.push( id * 10 + i );
            std::cout << "Producer " << id << " produced " << id * 10 + i << "\n";
        }
        cv.notify_one();  // 通知消费者
        std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
    }
}
// 消费者
void consumer( int id )
{
    while ( true )
    {
        std::unique_lock< std::mutex > lock( mtx );
        cv.wait( lock, [] { return !task_queue.empty() || done; } );
        if ( done && task_queue.empty() )
            break;
        int task = task_queue.front();
        task_queue.pop();
        lock.unlock();  // 提前释放锁,减少临界区
        std::cout << "Consumer " << id << " processed " << task << "\n";
        std::this_thread::sleep_for( std::chrono::milliseconds( 150 ) );
    }
}
int main()
{
    std::thread p1( producer, 1 );
    std::thread p2( producer, 2 );
    std::thread c1( consumer, 1 );
    std::thread c2( consumer, 2 );
    p1.join();
    p2.join();
    done = true;
    cv.notify_all();  // 通知所有消费者退出
    c1.join();
    c2.join();
}输出:
            
            
              bash
              
              
            
          
          Producer 1 produced 10
Consumer 2 processed 10Producer 
2 produced 20
Consumer 1 processed 20
Producer 1 produced 11
Producer 2 produced 21
Consumer 2 processed 11
Consumer 1 processed 21
Producer 1 produced 12
Producer 2 produced 22
Consumer 2 processed 12
Consumer 1 processed 22
Producer 1 produced 13
Producer 2 produced 23
Producer 1 produced 14
Producer 2 produced 24
Consumer 2 processed 13
Consumer 1 processed 23
Consumer 1 processed 14
Consumer 2 processed 24关键设计:
- 使用 done 标志优雅退出。
- cv.notify_one():一个任务唤醒一个消费者(避免惊群)。
- 消费后提前解锁,提高并发性。
六、带超时的等待:wait_for() 与 wait_until()
适用于"最多等待 N 秒"的场景。
            
            
              cpp
              
              
            
          
          std::unique_lock<std::mutex> lock(mtx);
if (cv.wait_for(lock, std::chrono::seconds(5), []{ return ready; })) 
{
    std::cout << "Condition met!\n";
}
else 
{
    std::cout << "Timeout! Condition not met.\n";
}- wait_for:相对时间(duration)
- wait_until:绝对时间(time_point)
✅ 返回值:若因条件满足而唤醒,返回 true;若超时,返回 false。
七、常见错误与最佳实践
❌ 错误1:忘记使用谓词(导致虚假唤醒崩溃)
            
            
              cpp
              
              
            
          
          // 危险!可能虚假唤醒后继续执行
cv.wait(lock);
// 此时 ready 可能仍为 false!✅ 正确:
            
            
              cpp
              
              
            
          
          cv.wait(lock, []{ return ready; });❌ 错误2:在未加锁时修改条件并通知
            
            
              cpp
              
              
            
          
          ready = true;           // ❌ 未加锁!
cv.notify_one();        // 可能通知丢失✅ 正确:
            
            
              cpp
              
              
            
          
          {
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}
cv.notify_one(); // 通知可在锁外,但修改必须在锁内📝 通知可以在锁外调用(C++ 允许),但修改共享状态必须在锁内。
❌ 错误3:使用 std::lock_guard 与 wait()
            
            
              cpp
              
              
            
          
          std::lock_guard<std::mutex> lock(mtx);
cv.wait(lock, ...); // ❌ 编译错误!wait 需要 unique_lock✅ 正确:
            
            
              cpp
              
              
            
          
          std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, ...);❌ 错误4:滥用 notify_all()
- 若只有一个线程能处理任务,用 notify_one() 更高效。
- notify_all() 适用于广播场景(如所有线程需响应"退出"信号)。
八、notify_one() vs notify_all()
| 场景 | 推荐 | 
|---|---|
| 任务队列(一个任务 → 一个消费者) | notify_one() | 
| 状态变更需所有线程响应(如 shutdown) | notify_all() | 
| 不确定有多少线程在等 | notify_all()(安全但低效) | 
九、总结:条件变量使用模板
            
            
              cpp
              
              
            
          
          // 共享状态
bool condition = false;
std::mutex mtx;
std::condition_variable cv;
// 等待线程
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return condition; });
    // 条件满足,处理逻辑
}
// 通知线程
{
    std::lock_guard<std::mutex> lock(mtx);
    condition = true;
}
cv.notify_one(); // 或 notify_all()最佳实践清单:
- 总是使用谓词版本的 wait()。
- 修改共享状态必须在互斥锁保护下。
- 使用 std::unique_lock(不是 lock_guard)。
- 优先用 notify_one(),除非需要广播。
- 考虑超时机制(wait_for)避免永久阻塞。
- 避免在持有锁时做耗时操作(提前 unlock)。