std::condition_variable条件变量和lock_guard、unique_lock

        用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。

 有意修改变量的线程必须

  1. 获得 std::mutex (常通过 std::lock_guard )
  2. 在保有锁时进行修改
  3. 在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)

即使共享变量是原子的,也必须在互斥下修改它。

任何有意在 std::condition_variable 上等待的线程必须

  1. 在与用于保护共享变量者相同的互斥上获得 std::unique_lock<std::mutex>
  2. 执行下列之一:
  1. 检查条件,是否为已更新或提醒它的情况
  2. 执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
  3. condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。

一:常用成员函数

通知

notify_one

通知一个等待的线程(公开成员函数),有多个线程时随机通知一个线程

notify_all

通知所有等待的线程(公开成员函数)
等待

wait

阻塞当前线程,直到条件变量被唤醒(公开成员函数)

wait_for

阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后(公开成员函数)

wait_until

阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点(公开成员函数)

wait:

void wait( std::unique_lock<std::mutex>& lock );
template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );

        包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用 unique_lock,因为wait函数的工作原理:

  • 当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者 notify_all唤醒当前线程;一旦当前线程获得通知(notify)wait()函数也是自动调用lock(),同理不 能使用lock_guard对象
  • 如果wait没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一 个线程调用notify_onenotify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程 会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。
  • 如果wait包含第二个参数,如果第二个参数不满足,那么wait将解锁互斥量并堵塞到本行,直到某 一个线程调用notify_onenotify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线 程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为falsewait对互斥量解锁并堵塞到本行,直到某 一个线程调用notify_onenotify_all为止,,如果为true,则进行后面的操作。
//2) 等价于
while (!pred()) {
    wait(lock);
}

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

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 不同的是, wait_for 可以执行一个时间段,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for 返回,剩下操作和 wait 类似。
wait_unti:
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 );

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 );
        与wait_for 类似,只是 wait_until 可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之 前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until 返回,剩下操作和 wait 类似。
notity_one:
        唤醒 正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待 的线程多余一个,则唤醒的线程是不确定的。
notiy_all:
        唤醒正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。

二:lock_guardunique_lock的使用和区别

        在作用域块期间占有互斥提供便利 RAII 风格机制,创建lock_guard/unique_lock对象时,会给互斥量上锁;离开lock_guard/unique_lock作用域时,会销毁lock_guard/unique_lock,并且释放互斥。

#include <thread>
#include <mutex>
#include <iostream>
 
int g_i = 0;
std::mutex g_i_mutex;  // 保护 g_i
 
void safe_increment()
{
    std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
 
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
 
    // g_i_mutex 在锁离开作用域时自动释放
}
 
int main()
{
    std::cout << "main: " << g_i << '\n';
 
    std::thread t1(safe_increment);
    std::thread t2(safe_increment);
 
    t1.join();
    t2.join();
 
    std::cout << "main: " << g_i << '\n';
}
unique_lock,lock_guard的区别:
  • unique_locklock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
  • unique_lock 可以进行临时解锁和再上锁,如在构造对象之后使用 lck.unlock() 就可以进行解锁, lck.lock()进行上锁,而不必等到析构时自动解锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值