条件变量condition_variable

C++标准库对条件变量有两套实现:std::condition_variable std::condition_variable_any 。 这两个实现都包含<condition_variable> 头文件的声明中。 两者都需要与一个互斥量一起才能工作(互斥量是为了同步); 前者仅限于与 std::mutex 一起工作, 而后者可以和任何满足最低标准的互斥量一起工作, 从而加上了_any的后缀。  

std::condition_variable 构造函数

condition_variable(); //default
condition_variable (const condition_variable&) = delete; // copy delete

看一个例子:

std::mutex mut;
std::queue<data_chunk> data_queue; // 1
std::condition_variable data_cond;
void data_preparation_thread()
{
    while(more_data_to_prepare())
    {
         data_chunk const data=prepare_data();
         std::lock_guard<std::mutex> lk(mut);
         data_queue.push(data); // 2
         data_cond.notify_one(); // 3
     }
} 
void data_processing_thread()
{
    while(true)
    {
         std::unique_lock<std::mutex> lk(mut); // 4
         data_cond.wait(lk,[]{return !data_queue.empty();}); // 5
         data_chunk data=data_queue.front();
         data_queue.pop();
         lk.unlock(); // 6
         process(data);
         if(is_last_chunk(data))
             break;
     }
}

首先, 你拥有一个用来在两个线程之间传递数据的队列。 当数据准备好时, 使 std::lock_guard 对队列上锁, 将准备好的数据压入队列中, 之后线程会对队列中的数据上锁。 然后调用 std::condition_variable notify_one()成员函数, 对等待的线程(如果有等待线程)进行通知 

在另外一侧, 你有一个正在处理数据的线程, 这个线程首先对互斥量上锁, 但在这std::unique_lock 要比 std::lock_guard 更加合适。 线程之后会调用 std::condition_variable 的成员函数wait(), 传递一个锁和一个lambda函数表达式(作为等待的条件) 在这个例子中, 简单的lambda函数[]{return !data_queue.empty();}会去检查data_queue是否不为空, 当data_queue不为空——那就意味着队列中已经准备好数据了。 

std::condition_variable::wait()

void wait (unique_lock<mutex>& lck); // unconditional 
template <class Predicate>
    void wait (unique_lock<mutex>& lck, Predicate pred); // predicate

std::condition_variable 提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。

在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。 这就是为什么用std::unique_lock 不使用 std::lock_guard 等待中的线程必须在等待期间解锁互斥量, 并在这这之后对互斥量再次上锁, 而 std::lock_guard 没有这么灵活。  

在第二种情况下(即设置了 Predicate),只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞

std::condition_variable::wait_for() 

template <class Rep, class Period>
  cv_status wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time); // unconditional 

template <class Rep, class Period, class Predicate>
       bool wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time, Predicate pred); // predicate

与 std::condition_variable::wait() 类似,不过 wait_for 可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for 返回,剩下的处理步骤和 wait() 类似。wait_for 的重载版本(predicte)的最后一个参数 pred 表示 wait_for 的预测条件,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。

std::condition_variable::wait_until

template <class Clock, class Duration>
  cv_status wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time); // unconditional 

template <class Clock, class Duration, class Predicate>
       bool wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time,
                        Predicate pred); // predicate 

与 std::condition_variable::wait_for 类似,但是 wait_until 可以指定一个时间点,在当前线程收到通知或者指定的时间点 abs_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_until 返回,剩下的处理步骤和 wait_until() 类似。

另外,wait_until 的重载版本(predicte)的最后一个参数 pred 表示 wait_until 的预测条件,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。

std::condition_variable::notify_one() 

唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。

std::condition_variable::notify_all()

唤醒所有的等待(wait)线程。如果当前没有等待线程,则该函数什么也不做。

 

posted @   小熊酱  阅读(386)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示