【C++11】condition_variable 条件变量

文章目录

一、条件变量的背景

  • 应用背景:在多线程程序中,我们经常遇到这样的场景:线程 A 需要等待某个条件成立(比如缓冲区非空),才能继续执行;而这个条件是由线程 B 在未来某个时刻设置的。

  • 如果用 轮询(busy-waiting) 的方式检查条件(比如 while (!GlobalReady) {}),会 浪费大量 CPU 资源,效率极低。

  • 条件变量(std::condition_variable) 就是为解决这个问题而生的:它允许一个或多个线程 挂起(阻塞) ,直到其他线程 通知(notify) 它们某个条件/状态可能已经满足,它才继续执行。

  • 💡 类比:医院诊室模型

并发概念 医院场景中的对应物 作用说明
共享资源 一个医生诊室(一次只能看一位病人) 临界区,需互斥访问
互斥锁(Mutex) 诊室的门 + 医生的"正在接诊"状态 进入前必须确认医生空闲并关门;保证独占
线程(Thread) 多个候诊病人(张三、李四、王五...) 并发请求使用资源的执行单元
条件变量(Condition Variable) 候诊区的叫号屏幕 + 护士的呼叫系统 不主动敲门,而是等被"精准通知"

二、条件变量详解

概述

  • 头文件#include <condition_variable>
  • 命名空间using namespace std;
  • ⚠️ 条件变量必须也只能与 unique_lock<mutex> 配合使用,以保证线程安全。
  • 条件变量本身不存储"条件"状态,它只是一个通知/等待机制。它不会记住你是否调用了 notify_one(),也不会自动检查 ready == true 这样的逻辑。它只负责: 释放锁让线程挂起,并在收到通知(或虚假唤醒)时上锁恢复执行 。因此,⚠️ 条件的状态 必须由程序员自己维护和检查。

▶ wait() 等待, 无谓词

基本用法

cpp 复制代码
void wait(std::unique_lock<std::mutex>& lock);
  • 作用
    • 内部自动释放锁,让当前线程挂起(阻塞);
    • 直到其他线程通过 notify_one() 或 notify_all() 唤醒后自动重新加锁。
  • 前提 :⚠️ 调用前此线程必须已持有锁。
  • 样例
cpp 复制代码
condition_variable cv;
mutex mtx;
unique_lock<mutex> lock(mtx);

cv.wait(lock);

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void worker() {
    cout << "Worker: 等待信号..."<< endl;
    unique_lock<mutex> lock(mtx); // unique_lock关联锁并上锁
    cv.wait(lock); // 释放锁,当前线程阻塞,直到被 notify
    cout << "Worker: 收到信号!开始工作。"<< endl;
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2));
    { // 这里的花括号是为了限制 lock_guard 的作用域,从而尽早释放互斥锁。
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 发送通知
    cout << "Boss: 已发送信号!"<< endl;
}

int main() {
    thread t1(worker);
    thread t2(boss);

    t1.join();
    t2.join();
    return 0;
}

缺陷

1、竞争条件(Race Condition)导致误判

  • 即使你收到了 notify,也不能保证在你被唤醒时,条件仍然成立。

  • 场景举例

    1. 假设有两个等待线程 A 和 B,一个通知线程 T。
    2. T 设置 ready = true 并调用 cv.notify_all(),让两个线程收到通知后都执行。
    3. 线程 A 先被唤醒,检查 ready == true,继续执行,并将 ready 重置为 false(比如消费了一个任务)。
    4. 线程 B 被唤醒(因为是 notify_all),但此时 ready 已经被 A 改回 false。如果 B 使用的是下面这种只检查一次的错误写法,就错了:
    cpp 复制代码
    if (!ready) {
    	cv.wait(lock);
    } 
    1. ⚠️ 此时可能出错,这是因为该线程在被唤醒后不会再检查 ready 的状态,如果 ready状态已经被线程A改变成了 false,往下执行可能会出错!因为我们假想的状态是 ready 为 true 且收到通知的时候才会往下执行。

2、虚假唤醒

  • 这是操作系统层面的行为:即使没有任何线程调用 notify,等待线程也可能被唤醒。POSIX 标准明确允许这种行为(出于性能或实现简化考虑)。例如,在某些多核系统上,中断、调度器行为等都可能触发虚假唤醒。
  • 后果:
    线程从 wait() 返回,但条件根本没变。
    如果没有重新检查条件,就会执行错误逻辑。
  • 📌 虚假唤醒不是 bug,而是标准允许的行为,程序必须能容忍它。

3、解决办法:while 循环判断状态

  • 尽管当前线程会被 竞争条件/虚假唤醒 所影响,但是我们可以通过如下while 循环结构避免这种影响:
    • 存在竞争条件时,条件 task_ready 可能被其他线程修改置为 false。但是当前线程在 cv.wait(lock); 执行结束返回后,while 循环这个代码结构内还是会执行一次 task_ready 的状态判断,进行二次确认,此时 task_ready 若还为false,则会继续加锁线程阻塞等待通知。
    • 存在虚假唤醒时,条件 task_ready 可能还未被修改。while 代码块的具体细节和上述保持一致。
cpp 复制代码
while (!task_ready) {       // ← 关键:while 循环
	cv.wait(lock);           // 可能因 notify 或虚假唤醒返回
}
除了以上 while 结构解决办法,有谓词的 wait 也可以解决竞争条件和 虚假唤醒导致的误判现象。

▶ wait() 等待, 有谓词

基本用法

cpp 复制代码
template< class Predicate >
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
  • 谓词 pred

    • 谓词 pred 是一个可调用对象(如 lambda、函数指针、函数对象等),可以理解为是一个 预期条件, 不接受参数,返回 bool 类型
    • 谓词中通常访问受互斥锁保护的共享状态(如 ready 变量) ,因此 其必须在持有锁时才能安全读取------而 wait 的设计恰好保证了这一点:每次检查谓词前都已加锁。
  • 作用

    • 内部自动释放锁,让当前线程挂起;
    • 当被 notify_one() 或 notify_all() 唤醒后,会先重新 获取/加 锁;然后检查谓词 pred 是否为 true,只有当谓词返回 true 时,wait 才会返回;否则继续阻塞等待下一次通知。
  • 前提 :⚠️ 调用前此线程必须已持有锁。

强烈建议使用带谓词版本 ,避免手动处理虚假唤醒和竞态条件。

  • 样例:
cpp 复制代码
condition_variable cv;
mutex mtx;
unique_lock<mutex> lock(mtx);

cv.wait(lock, []{ return ready; });

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void worker() {
    cout << "Worker: 等待信号..."<< endl;
    unique_lock<mutex> lock(mtx); // unique_lock关联锁并上锁
    cv.wait(lock, []{ return ready; }); // 带谓词版本:自动处理虚假唤醒和条件重检
    cout << "Worker: 收到信号!开始工作。"<< endl;
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2));
    { // 这里的花括号是为了限制 lock_guard 的作用域,从而尽早释放互斥锁。
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 发送通知
    cout << "Boss: 已发送信号!"<< endl;
}

int main() {
    thread t1(worker);
    thread t2(boss);

    t1.join();
    t2.join();
    return 0;
}

▶ wait_for() 相对时间等待, 无谓词

基本用法

cpp 复制代码
// 无谓词 wait_for()
template< class Rep, class Period >
std::cv_status wait_for(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::duration<Rep, Period>& rel_time
);
  • 作用内部自动释放锁,让当前线程挂起(阻塞);直到其他线程通过 notify_one()/notify_all() 唤醒 或者 超时时间 rel_time 到期 后自动重新加锁。
    • 当被通知唤醒(但不一定条件成立 ,可能虚假唤醒),返回 std::cv_status::no_timeout:;
    • 当超时了,返回 std::cv_status::timeout
  • ⚠️ 注意:无谓词版本 不检查条件是否真正满足,需手动处理虚假唤醒!
  • 样例:
cpp 复制代码
condition_variable cv;
mutex mtx;
unique_lock<mutex> lock(mtx);

auto status = cv.wait_for(lock, chrono::seconds(1));

if (status == cv_status::timeout) {
    cout << "超时!" << endl;
} else {
    cout << "收到信号!"<< endl;
}

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void worker() {
    unique_lock<mutex> lock(mtx); // unique_lock关联锁并上锁
    cout << "Worker: 等待信号..."<< endl;
    auto start = chrono::steady_clock::now();
    auto status = cv.wait_for(lock, chrono::seconds(1)); // ✅️ 测试超时等待
    //auto status = cv.wait_for(lock, chrono::seconds(5)); // ✅️ 测试通知唤醒
    auto end = chrono::steady_clock::now();

    // 此时 lock 仍然是 locked!
    auto elapsed = chrono::duration_cast<chrono::milliseconds>(end - start);
    if (status == cv_status::timeout) {
        cout << "超时!等待了 " << elapsed.count() << " 毫秒"<< endl;
        return;
        // 可以安全访问受 mtx 保护的数据(虽然本例中没有)
    }
    cout << "Worker: 收到信号!开始工作。"<< endl;
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2));
    { // 这里的花括号是为了限制 lock_guard 的作用域,从而尽早释放互斥锁。
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 发送通知
    cout << "Boss: 已发送信号!"<< endl;
}

int main() {
    thread t1(worker);
    thread t2(boss);

    t1.join();
    t2.join();
    return 0;
}


▶ wait_for() 相对时间等待, 有谓词

基本用法⭐⭐

cpp 复制代码
// 有谓词 wait_for()
template< class Rep, class Period, class Predicate >
bool wait_for(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::duration<Rep, Period>& rel_time,
    Predicate pred
);
  • 作用内部自动释放锁,让当前线程挂起(阻塞);直到其他线程通过 notify_one()/notify_all() 唤醒 或者 超时时间 rel_time 到期 后自动重新加锁,然后调用 pred() 检查条件。

    • 无论是被通知唤醒还是超时,只要 pred() 条件为真,则返回 true
    • 当超时且条件为 false 时,则返回 false
    • 当被通知唤醒但是条件为false时,则继续wait_for等待,不返回
  • 样例:

cpp 复制代码
condition_variable cv;
mutex mtx;
unique_lock<mutex> lock(mtx);

bool result = cv.wait_for(lock, chrono::seconds(3), []{ return ready; });

if (result) {
	cout << "收到信号!"<< endl;
} else {
	cout << "超时!"<< endl;
}

强烈建议使用带谓词版本 ,避免手动处理虚假唤醒和竞态条件。

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void worker() {
    unique_lock<mutex> lock(mtx); // unique_lock关联锁并上锁
    cout << "Worker: 等待信号..."<< endl;
    auto start = chrono::steady_clock::now();

    bool result = cv.wait_for(lock, chrono::seconds(3), []{ return ready; }); // ✅️ 通知唤醒,返回 true
    // bool result = cv.wait_for(lock, chrono::seconds(1), []{ return ready; }); // ✅️ 超时唤醒 且条件为 false,返回 false

    if (result) {
        cout << "Worker: 收到信号!开始工作。"<< endl;
    } else {
        cout << "Worker: 超时!放弃等待。\n";
    }
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2));
    { // 这里的花括号是为了限制 lock_guard 的作用域,从而尽早释放互斥锁。
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 发送通知
    cout << "Boss: 已发送信号!"<< endl;
}

int main() {
    thread t1(worker);
    thread t2(boss);

    t1.join();
    t2.join();
    return 0;
}



▶ wait_until() 绝对时间等待, 无谓词

💡 提示wait_until() 使用的是绝对时间点(如"2026-02-18 22:45:00"),而 wait_for() 使用的是相对时长(如"再等3秒"),两者功能等价,只是表达方式不同。

基本用法

cpp 复制代码
// 无谓词 wait_until()
template< class Clock, class Duration >
std::cv_status wait_until(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::time_point<Clock, Duration>& abs_time
);
  • 作用内部自动释放锁,让当前线程挂起(阻塞);直到其他线程通过 notify_one()/notify_all() 唤醒,或者 系统时间达到指定的绝对时间点 abs_time 后自动重新加锁。
    • 当被通知唤醒(但不一定条件成立 ,可能虚假唤醒),返回 std::cv_status::no_timeout
    • 当到达 abs_time 仍未被唤醒,则超时,返回 std::cv_status::timeout
  • ⚠️ 注意:无谓词版本 不检查条件是否真正满足,需手动处理虚假唤醒!
  • 样例:
cpp 复制代码
condition_variable cv;
mutex mtx;
unique_lock<mutex> lock(mtx);

auto deadline = chrono::steady_clock::now() + chrono::seconds(1);// ✅️ 最多等到未来1秒
auto status = cv.wait_until(lock, deadline);

if (status == cv_status::timeout) {
    cout << "超时!" << endl;
} else {
    cout << "收到信号!"<< endl;
}

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void worker() {
    unique_lock<mutex> lock(mtx);
    cout << "Worker: 等待信号..." << endl;

    auto deadline = chrono::steady_clock::now() + chrono::seconds(3);// ✅️ 通知唤醒,最多等到未来3秒
    //auto deadline = chrono::steady_clock::now() + chrono::seconds(1);// ✅️ 超时,最多等到未来1秒
    auto status = cv.wait_until(lock, deadline);

    if (status == cv_status::timeout) {
        cout << "超时!未收到信号。" << endl;
    } else {
        cout << "Worker: 收到信号!开始工作。" << endl;
    }
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2));
    {
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
    cout << "Boss: 已发送信号!" << endl;
}

int main() {
    thread t1(worker);
    thread t2(boss);

    t1.join();
    t2.join();
    return 0;
}



▶ wait_until() 绝对时间等待, 有谓词

基本用法

cpp 复制代码
// 有谓词 wait_until()
template< class Clock, class Duration, class Predicate >
bool wait_until(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::time_point<Clock, Duration>& abs_time,
    Predicate pred
);
  • 作用内部自动释放锁,让当前线程挂起(阻塞);直到其他线程通过 notify_one()/notify_all() 唤醒,或 系统时间达到 abs_time,然后重新加锁并调用 pred() 检查条件。
    • 无论是被通知唤醒还是超时,只要 pred() 条件为真,则返回 true
    • 当超时且条件为 false 时,则返回 false
    • 当被通知唤醒但是条件为false时,则继续wait_for等待,不返回
  • 样例:
cpp 复制代码
condition_variable cv;
mutex mtx;
unique_lock<mutex> lock(mtx);

auto deadline = chrono::steady_clock::now() + chrono::seconds(3);// ✅️ 最多等到未来3秒
bool result = cv.wait_until(lock, deadline, []{ return ready; });

if (result) {
	cout << "收到信号!"<< endl;
} else {
	cout << "超时!"<< endl;
}

强烈建议使用带谓词版本 ,避免手动处理虚假唤醒和竞态条件。

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void worker() {
    unique_lock<mutex> lock(mtx);
    cout << "Worker: 等待信号..." << endl;

    auto deadline = chrono::steady_clock::now() + chrono::seconds(3);// ✅️ 通知唤醒,最多等到未来3秒
    //auto deadline = chrono::steady_clock::now() + chrono::seconds(1);// ✅️ 超时,最多等到未来1秒

    bool result = cv.wait_until(lock, deadline, []{ return ready; });

    if (result) {
        cout << "Worker: 条件满足!开始工作。" << endl;
    } else {
        cout << "Worker: 超时!" << endl;
    }
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2));
    {
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
    cout << "Boss: 已发送信号!" << endl;
}

int main() {
    thread t1(worker);
    thread t2(boss);

    t1.join();
    t2.join();
    return 0;
}



▶ notify_one() 唤醒一个等待线程

基本用法

cpp 复制代码
void notify_one() noexcept;
  • 作用唤醒一个等待线程 ,但具体是哪一个,由操作系统线程调度器决定,程序无法控制,也不应依赖其顺序。
    • 如果当前没有线程在等待,notify_one() 什么也不做(不会累积通知)。
    • ⚠️被唤醒的线程 不一定立即运行 ------它必须先成功重新获得互斥锁。
  • ⚠️ 不能替代谓词检查 :即使收到通知,也可能是虚假唤醒,因此 必须配合谓词使用,尤其是在无谓词的 wait 中。
  • 推荐在修改状态后通知,确保被唤醒的线程看到的是最新的、一致的状态。 若先通知再改状态,等待线程可能被唤醒但发现条件仍不满足,导致逻辑错误或额外等待。
  • 样例:
cpp 复制代码
{
	// 1、上锁,修改状态
	lock_guard<mutex> lock(mtx);
	ready = true;
}
// 2、通知单个线程
cv.notify_one();

实例

参考前述内容的实例。


▶ notify_all() 唤醒所有等待线程

基本用法

cpp 复制代码
void notify_all() noexcept;
  • 作用唤醒所有等待线程
    • 如果没有线程在等待,notify_all() 什么也不做
    • ⚠️所有被唤醒的线程 不会同时运行依次竞争互斥锁,逐个获得并执行
  • notify_one() 一样,不需要持有互斥锁即可调用;
  • ⚠️ 仍需配合谓词使用! 即使收到通知,线程也必须检查条件是否真正满足(防止虚假唤醒或状态已变化)。
  • 何时使用 notify_all() :当 多个线程都需要响应同一个事件 时,例如:
    配置更新,所有工作线程需重载参数;
    程序即将退出,通知所有后台线程清理资源;
    广播式任务(如"所有人暂停")。
  • 样例:
cpp 复制代码
{
	// 1、上锁,修改状态
	lock_guard<mutex> lock(mtx);
	ready = true;
}
// 2、通知多个线程
cv.notify_all();

实例

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false; // 全局就绪标志

void worker(int id) {
    unique_lock<mutex> lock(mtx);
    cout << "Worker " << id << ": 等待信号..." << endl;

    // 带谓词等待:直到 ready == true 或超时(3秒)
    bool result = cv.wait_for(lock, chrono::seconds(3), []{ return ready; });

    if (result) {
        cout << "Worker " << id << ": 收到信号!开始工作。" << endl;
    } else {
        cout << "Worker " << id << ": 超时!放弃等待。" << endl;
    }
}

void boss() {
    this_thread::sleep_for(chrono::seconds(2)); // 模拟准备时间

    {
        lock_guard<mutex> lock(mtx);
        ready = true; // 设置就绪状态
    }

    cv.notify_all(); // ✅ 唤醒所有等待的 worker 线程
    cout << "Boss: 已广播信号!" << endl;
}

int main() {
    const int NUM_WORKERS = 3;
    vector<thread> workers;

    // 启动多个工作线程
    for (int i = 0; i < NUM_WORKERS; ++i) {
        workers.emplace_back(worker, i);
    }

    // 启动 boss 线程
    thread t_boss(boss);

    // 等待所有线程完成
    for (auto& t : workers) {
        t.join();
    }
    t_boss.join();

    return 0;
}

三、小结

  • 务必使用带谓词的 wait()/wait_for()/wait_until()
  • 避免频繁 notify_all,优先用 notify_one。
相关推荐
智者知已应修善业1 小时前
【排列顺序判断是否一次交换能得到升序】2025-1-28
c语言·c++·经验分享·笔记·算法
fpcc2 小时前
并行编程实战——CUDA编程的内存建议
c++·cuda
瓦特what?3 小时前
希 尔 排 序
开发语言·c++
落羽的落羽3 小时前
【Linux系统】磁盘ext文件系统与软硬链接
linux·运维·服务器·数据库·c++·人工智能·机器学习
StandbyTime4 小时前
《算法笔记》练习记录-2.5-问题 D: 习题6-12 解密
c++·算法笔记
ADDDDDD_Trouvaille4 小时前
2026.2.18——OJ86-88题
c++·算法
_nirvana_w_4 小时前
Qt项目链接库时遇到的坑:-l选项的正确用法
开发语言·c++·qt·qt框架·elawidgettools
我命由我123454 小时前
Visual Studio - Visual Studio 修改项目的字符集
c语言·开发语言·c++·ide·学习·visualstudio·visual studio
郝学胜-神的一滴4 小时前
Python变量本质:从指针哲学到Vibe Coding优化
开发语言·c++·python·程序人生