写点什么

一个 cpp 协程库的前世今生(十五)递归互斥量

作者:SkyFire
  • 2022 年 1 月 10 日
  • 本文字数:1513 字

    阅读完需:约 5 分钟

一个cpp协程库的前世今生(十五)递归互斥量

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


GitHub - skyfireitdiy/cocpp at cocpp-0.1.0


前面讲了互斥量,信号量和条件变量的实现。本文再讲一下递归互斥量的实现。


递归互斥量,顾名思义就是可以递归上锁的互斥量,同一个协程下可以反复上锁,反复解锁。只要保证解锁的次数和上锁的次数一致就可以了。

接口

接下来我们看一下递归互斥量的接口定义(include/cocpp/sync/co_recursive_mutex.h):


class co_recursive_mutex final : private co_noncopyable{private:    co_spinlock         spinlock__;          // 互斥锁    co_ctx*             owner__ { nullptr }; // 当前mutex的所有者    std::deque<co_ctx*> wait_deque__;        // 等待队列    unsigned long long  lock_count__ { 0 };  // 锁定次数public:    void lock();     // 加锁    void unlock();   // 解锁    bool try_lock(); // 尝试加锁};
复制代码


接口与互斥量的接口类似,这里就不做赘述。


重点介绍一下几个成员。


  • spinlock__:保护其他成员的自旋锁。

  • owner__ :当前互斥量的持有者。

  • wait_deque__:等待在这个互质量上的协程列表。

  • lock_count__ :加锁深度(因为需要解锁和上锁的次数保持一致)。

实现

递归互斥量的实现和普通的互斥量基本上是一样的,只是多了一个计数。


先看看加锁的流程:

lock

加锁流程的定义位于 source/cocpp/sync/co_recursive_mutex.cpp:


void co_recursive_mutex::lock(){    auto ctx = co::current_ctx();    spinlock__.lock();    CoDefer([this] { spinlock__.unlock(); });    if (owner__ != ctx && owner__ != nullptr)    {        ctx_enter_wait_state__(ctx, CO_RC_TYPE_RECURSIVE_MUTEX, this, wait_deque__);        lock_yield__(spinlock__, [this, ctx] { return owner__ != ctx && owner__ != nullptr; });    }
owner__ = ctx; ++lock_count__;}
复制代码


整个流程的逻辑如下:


  1. 获取当前的协程上下文

  2. 加锁保护内部成员数据(并在函数返回时解锁)

  3. 如果当前互斥量有持有者,但不是当前线程,就进入等待状态。

  4. 否则将持有者设置为当前协程,并且累加计数。


这个和一般的互质量不一样的地方是普通的互斥量在判断能否直接加锁时判断的是有没有人持有,而递归互斥量,不仅判断有没有人持有,还需要判断持有人是不是自己,如果持有人是自己也是可以加锁成功的。


下面就不介绍 try_lock 接口了,与 lock 基本相同,只是直播加锁失败不会进入等待,而是返回 false。

unlock

接下来看一下解锁的接口实现,位于 source/cocpp/sync/co_recursive_mutex.cpp:


void co_recursive_mutex::unlock(){    auto ctx = co::current_ctx();    spinlock__.lock();    CoDefer([this] { spinlock__.unlock(); });    if (owner__ != ctx)    {        CO_O_ERROR("ctx is not owner, this ctx is %p, owner is %p", ctx, owner__);        throw co_error("ctx is not owner[", ctx, "]");    }
--lock_count__; if (lock_count__ != 0) { return; }
owner__ = nullptr; wake_front__(wait_deque__);}
复制代码


整个流程解释如下:


  1. 获取当前协程上下文。

  2. 加锁保护成员变量(同样在函数返回时解锁)。

  3. 判断持有者是不是自己,如果持有者不是自身却调用解锁接口,会抛出异常。

  4. 减少锁定计数。

  5. 如果计数不为 0,说明互斥量还是被自身持有,返回。

  6. 如果计数为 0 了,则互斥量当前没有人持有了,将持有者设置为 nullptr,并且唤醒等待列表中的第 1 个协程去抢占锁。


上面的 wake_front__函数实现,在讲互斥量的时候已经分析过,本文不再赘述。

总结

本文介绍了递归互斥量的实现,基本上和普通的互斥量很类似,只是多了一个计数,也很好理解。

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

SkyFire

关注

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

会一点点cpp的苦逼码农

评论

发布
暂无评论
一个cpp协程库的前世今生(十五)递归互斥量