C++ 条件变量

C++ 条件变量

目录

  1. #1-stdcondition_variable-核心函数详解
  2. #2-stdcondition_variable_any-核心函数详解
  3. #3-等待函数详解
  4. #4-通知函数详解
  5. #5-条件变量的正确使用模式
  6. #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;
}

关键细节

  1. 原子性操作wait() 调用是原子的 - 先释放锁,然后阻塞线程
  2. 虚假唤醒:线程可能在没有通知的情况下被唤醒,必须重新检查条件
  3. 锁的状态:进入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_lock
  • rel_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++条件变量教程涵盖了所有核心函数的用法、注意事项和最佳实践,包括正确的使用模式、常见陷阱的避免方法以及性能优化技巧。

相关推荐
_OP_CHEN1 小时前
算法基础篇:(十二)基础算法之倍增思想:从快速幂到大数据运算优化
大数据·c++·算法·acm·算法竞赛·倍增思想
xie0510_1 小时前
C++入门
c++
AA陈超1 小时前
ASC学习笔记0027:直接设置属性的基础值,而不会影响当前正在生效的任何修饰符(Modifiers)
c++·笔记·学习·ue5·虚幻引擎
羚羊角uou1 小时前
【C++】智能指针
开发语言·c++
杜子不疼.1 小时前
【C++】哈希表基础:开放定址法 & 什么是哈希冲突?
c++·哈希算法·散列表
代码不停1 小时前
网络原理——初识
开发语言·网络·php
04aaaze1 小时前
C++(C转C++)
c语言·c++·算法
不会c嘎嘎2 小时前
C++ -- list
开发语言·c++
老鱼说AI2 小时前
BPE编码从零开始实现pytorch
开发语言·人工智能·python·机器学习·chatgpt·nlp·gpt-3