写点什么

一个 cpp 协程库的前世今生(十四)信号量与条件变量

作者:SkyFire
  • 2022 年 1 月 09 日
  • 本文字数:2151 字

    阅读完需:约 7 分钟

一个cpp协程库的前世今生(十四)信号量与条件变量

为了防止大家找不到代码,还是先贴一下项目链接:


GitHub - skyfireitdiy/cocpp at cocpp-0.1.0


上一篇文章介绍了互斥量的实现,本篇文章继续介绍信号量和条件变量。


信号量分为计数信号量和二值信号量,二值信号量是计数信号量的一个特化。

条件变量

条件变量的定义非常简单,在 include/cocpp/sync/co_condition_variable.h 中:


using co_condition_variable = std::condition_variable_any; // 条件变量
复制代码


可以看到,co_condition_variable 其实就是 std::condition_variable_any 的别名,std::condition_variable_any 可以等待任何带有 lock 和 unlock 接口的对象,因此我们就可以将实现的协程锁作为他的入参对象,它本身并不区分是哪一种类型的锁。

计数信号量

计数信号量的典型应用场景是生产者消费者,信号量的计数代表着资源的个数。

接口

信号量的接口位于 include/cocpp/sync/co_counting_semaphore.h


template <std::ptrdiff_t LeastMaxValue>class co_counting_semaphore final : private co_noncopyable{private:    co_mutex              mu__;            // 互斥锁    co_condition_variable empty_cond__;    // 空闲条件变量    co_condition_variable full_cond__;     // 满条件变量    std::ptrdiff_t        desired__;       // 期望值    void                  release_one__(); // 释放一个信号量public:    void acquire();                          // 获取一个信号量    void release(std::ptrdiff_t update = 1); // 释放一个信号量    bool try_acquire() noexcept;             // 尝试获取一个信号量    template <class Rep, class Period>    bool try_acquire_for(const std::chrono::duration<Rep, Period>& rel_time); // 尝试获取一个信号量    template <class Clock, class Duration>    bool try_acquire_until(const std::chrono::time_point<Clock, Duration>& abs_time); // 尝试获取一个信号量    constexpr explicit co_counting_semaphore(std::ptrdiff_t desired);                 // 构造函数    static constexpr std::ptrdiff_t max() noexcept;                                   // 最大值};
复制代码


信号量的接口比互斥量要复杂的多,接下来我们看一下这些接口的用途。


  • acquire:获取一个资源,计数减 1,在获取不到时会阻塞,直到获取成功为止

  • release:释放一个或多个资源,参数的默认为 1,意味着释放一个资源,如果资源数量已经到达 LeastMaxValue,则会阻塞,直到资源被消费。

  • try_acquire:尝试获取一个资源,如果获取不到,返回 false。

  • try_acquire_for 与 try_acquire_until:在指定时间段内或者是指定时间点前尝试获取资源,如果一直获取不到,返回 false

  • max:返回最大可容纳的资源数量,这是一个 constexpr 函数

  • 构造函数:构造函数带有一个值,表示默认的资源数量


接下来我们再看那几个私有成员变量


  • mu__:用来保护资源计数的互斥量

  • empty_cond__:资源为空时,等待的条件变量

  • full_cond__:资源为满时,等待的条件变量

  • desired__:当前的资源计数

  • release_one__:释放一个资源

实现

信号量的实现比较复杂,接下来我们一一来看。

acquire

acquire 的实现如下(include/cocpp/sync/co_counting_semaphore.h):


template <std::ptrdiff_t LeastMaxValue>void co_counting_semaphore<LeastMaxValue>::acquire(){    std::unique_lock<co_mutex> lock(mu__);    assert(desired__ >= 0 && desired__ <= LeastMaxValue);    while (desired__ == 0)    {        empty_cond__.wait(lock);    }    --desired__;    full_cond__.notify_one();}
复制代码


此处先获取到操作计数的互斥量。


然后判断当前是否有可用的资源,如果没有,就使用条件变量等待,直到有可用资源为止。


然后将可用资源数量减 1。


然后通知 full_cond__对应的等待者可以放资源了。


接下来看一下 release 的做法。

release

实现如下(include/cocpp/sync/co_counting_semaphore.h):


template <std::ptrdiff_t LeastMaxValue>void co_counting_semaphore<LeastMaxValue>::release_one__(){    std::unique_lock<co_mutex> lock(mu__);    assert(desired__ >= 0 && desired__ <= LeastMaxValue);    while (desired__ == LeastMaxValue)    {        full_cond__.wait(lock);    }    ++desired__;    empty_cond__.notify_one();}
template <std::ptrdiff_t LeastMaxValue>void co_counting_semaphore<LeastMaxValue>::release(std::ptrdiff_t update){ for (std::ptrdiff_t i = 0; i < update; ++i) { release_one__(); }}
复制代码


其中 release_one__与 acquire 是类似的,这里不做赘述。


可以看到,因为 release 支持同时释放多个资源,因此它是多次调用 release_one__。


其余的几个接口都很简单,这里就不一一介绍了。

二值信号量

顾名思义,二值信号量就是只有一个空间的信号量,定义如下(include/cocpp/sync/co_binary_semaphore.h):


using co_binary_semaphore = co_counting_semaphore<1>; // 二进制信号量
复制代码

总结

本文介绍的条件变量与信号量的实现。其中条件变量其实就是 std::condition_variable_any,并不关心底层的操作对象。而计数信号量是典型的生产者消费者写法,二值信号量是计数信号量的一个特例。

发布于: 刚刚阅读数: 2
用户头像

SkyFire

关注

这个cpper很懒,什么都没留下 2018.10.13 加入

会一点点cpp的苦逼码农

评论

发布
暂无评论
一个cpp协程库的前世今生(十四)信号量与条件变量