c++ 条件变量使用详解 wait_for wait_unitl 虚假唤醒

c++ 条件变量使用详解

std::condition_variable

  • 线程间同步的条件变量类。允许一个或多个线程在满足特定条件之前等待,同时允许其他线程通知、唤醒等待的线程。

成员函数

  • notify_one:通知一个等待的线程。
  • notify_all:通知所有等待的线程。
  • wait:阻塞当前线程,直到条件变量被唤醒。
  • wait_until:阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点。
  • wait_for:阻塞当前线程,直到条件变量被唤醒,或直到指定时限时长后。

一般使用方法

  • 对于修改条件后通知其他线程的线程:
    • 获得 std::mutex,通常使用 std::lock_guard;
    • 在成功获得锁后,修改条件;
    • 使用 notify_one 或 notify_all 唤醒等待的线程;
  • 对于等待唤醒的线程:
    • 获得 std::mutex,通常使用 std::unique_lock;
    • 使用 wait,wait_for,wait_until 等待唤醒,此时线程释放互斥,并进入阻塞;
    • 被其他线程唤醒,被虚假唤醒或者等待超时,线程自动重获得互斥,此时应检查条件是否成立,若不成立,则继续等待或者返回;

虚假唤醒

  • 指线程在等待期间,重新获得互斥,而这一行为却不来源于其他任何线程的通知,则称之为虚假唤醒。
  • 按照 c++ 标准规定,虚假唤醒出现的数量以及比率都不确定。

notify_one notify_all 使用注意

  • 在唤醒其他线程时,通知线程一般无需持有互斥,因为被唤醒线程醒来后,一旦不能立即获得互斥,将会再次阻塞。

wait 函数简介

  • 函数原型:

    void wait(std::unique_lock<std::mutex>& lock);
    // 若发生虚假唤醒,wait 之后的代码将会被执行
    
    template<class Predicate>
    void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
    /** 相当于
    while (!pred()) {
        通过检查 pred 返回值,可以避免虚假唤醒
        wait(lock);
    }
    **/ 
    
    • lock:互斥体,必须被当前线程锁定;
    • pred:返回 ​false,则继续等待。其签名为 bool pred();
  • 使当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可选地循环直至满足 pred。

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>& timeout_time);
    // 被其他线程唤醒或发生虚假唤醒,未超时,返回 std::cv_status::no_timeout
    // 超时则返回 std::cv_status::timeout
    
    template<class Clock, class Duration, class Pred>
    bool wait_until(std::unique_lock<std::mutex>& lock,
                    const std::chrono::time_point<Clock, Duration>& timeout_time,
                    Pred pred);
    /** 相当于
    while (!pred()) {
        通过检查 pred 返回值,可以避免虚假唤醒
        if (wait_until(lock, timeout_time) == std::cv_status::timeout) {
            return pred();
        }
    }
    return true;
    **/
    
    • lock:互斥体,必须被当前线程锁定;
    • timeout_time:表示停止等待时间的 std::chrono::time_point 类型对象;
    • pred:返回 ​false,则继续等待。其签名为 bool pred();
  • 使当前线程阻塞直至条件变量被通知、抵达指定时间或虚假唤醒发生,可选的循环直至满足 pred。

  • 使用注意:时钟最好使用稳定时钟,即计时速率恒定且无法调整的时钟。

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);
    // 被其他线程唤醒或发生虚假唤醒,未超时,返回 std::cv_status::no_timeout,
    // 超时则返回 std::cv_status::timeout
    
    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);
    // 相当于
    // wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));
    
    • lock:互斥体,必须被当前线程锁定;
    • rel_time:表示等待所耗的最大时间的 std::chrono::duration 类型对象。rel_time 必须足够小,以在加到 std::chrono::steady_clock::now() 时不溢出;
    • pred:返回 ​false,则继续等待。其签名为 bool pred();
  • 使当前线程阻塞直至条件变量被通知、抵达指定时间或虚假唤醒发生,可选的循环直至满足 pred。

  • 由于操作系统调度或资源争议,此函数可能阻塞长于 rel_time。

示例代码

  • 线程1修改变量,线程2等待变量被修改。

    #include <iostream>
    #include <condition_variable>
    #include <thread>
    #include <chrono>
    
    using namespace std::chrono_literals;
    
    int i = 0;
    bool changed{false};
    std::condition_variable cv;
    std::mutex cv_m;
    
    void wait()
    {
        std::unique_lock<std::mutex> lk(cv_m);
        // std::chrono_literals::100ms, c++14 引入
        if (cv.wait_for(lk, 100ms, [](){return changed;})) {
            printf("finished waiting, i is %d\n", i);
        } else {
            printf("wait timeout, i is %d\n", i);
        }
    }
    
    void notify()
    {
        {
            std::this_thread::sleep_for(50ms);
            std::lock_guard<std::mutex> lk(cv_m);
            i = 10;
            changed = true;
            printf("change finished\n");
        }
        cv.notify_one();
    }
    
    int main()
    {
        std::thread t1(notify);
        std::thread t2(wait);
        t1.join();
        t2.join();
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

专注的罗哈哈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值