跳转到内容

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);
}

线程同步状态图[编辑 | 编辑源代码]

stateDiagram [*] --> 等待条件 等待条件 --> 获取锁: notify_one/all 获取锁 --> 检查条件 检查条件 --> 等待条件: 条件不满足 检查条件 --> 执行操作: 条件满足 执行操作 --> [*]

高级主题[编辑 | 编辑源代码]

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)理论描述: {wait:释放锁进入等待队列notify:从等待队列移入就绪队列notifyAll:所有等待线程移入就绪队列

最佳实践[编辑 | 编辑源代码]

1. 总是使用RAII管理锁(如lock_guardunique_lock) 2. 通知前尽量释放锁 3. 优先使用带谓词的wait版本 4. 避免在持有锁时执行耗时操作 5. 考虑使用std::atomic简化简单场景