写点什么

一个 cpp 协程库的前世今生(十二)自旋锁

作者:SkyFire
  • 2022 年 1 月 06 日
  • 本文字数:1618 字

    阅读完需:约 5 分钟

一个cpp协程库的前世今生(十二)自旋锁

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


GitHub - skyfireitdiy/cocpp at cocpp-0.1.0


今天来讲一下自旋锁的实现。


cocpp 中的自旋锁分为两种,一种是在协程中用的,一种是在线程中用的。


区别在于在协程中使用的自旋锁在获取不到锁时会切换协程,而在线程中使用的自旋锁在获取不到锁时会让出 CPU,相当于切换线程。


接下来我们来看一下接口与实现。

接口

自旋锁的接口位于 include/cocpp/sync/co_spinlock.h


class co_spinlock final : private co_noncopyable{public:    enum class lock_type    {        in_coroutine, // 在协程中        in_thread     // 在线程中    };
private: const lock_type lock_type__; // 锁类型 std::atomic<bool> locked__ { false }; // 是否锁定public: co_spinlock(lock_type lt = lock_type::in_coroutine); // 构造 void lock(); // 加锁 bool try_lock(); // 尝试加锁 void unlock(); // 解锁};
复制代码


对外提供 3 个接口(不包括构造函数)。


lock 接口对自旋锁上锁,直到上锁成功为止。


try_lock 接口尝试对自旋锁上锁,返回上锁是否成功的结果。


unlock 接口对自旋锁解锁。


注意:自旋锁的实现是为了支撑其他的同步实现,接口不对最终用户提供。

实现

自旋锁的实现非常简单,就是利用原子操作比较交换语义完成的。

lock

先看一下上锁接口 lock(source/cocpp/sync/co_spinlock.cpp):


void co_spinlock::lock(){    bool lk = false;    while (!locked__.compare_exchange_strong(lk, true))    {        if (lock_type__ == lock_type::in_coroutine)        {            this_co::yield();        }        else        {            std::this_thread::yield();        }        lk = false;    }}
复制代码


成员变量 locked__记录的当前是否已上锁,如果已上锁,则这个值为 true,否则为 false。


利用 compare_exchange_strong,进行原子地比较与修改加锁状态。


如果加锁失败,根据锁的类型决定下一步操作,如果是在协程中使用的锁,就调用 this_co::yield,否则调用 std::this_thread::yield。


其实 this_co::yield 就是协程切换(source/cocpp/interface/co_this_co.cpp):


void yield(){    co::current_env()->schedule_switch();}
复制代码


为什么每一轮循环都要把局部变量 lk 设置为 false 呢?这是因为 compare_exchange_strong 函数如果比较失败的时候,会将第一个参数修改为当前的值,也就是 true,所以每一次循环都要重置一下。

try_lock

try_lock 的实现和 lock 基本类似,只是不会反复重试,井比较交换一次,返回结果即可(source/cocpp/sync/co_spinlock.cpp)。


bool co_spinlock::try_lock(){    bool lk = false;    return locked__.compare_exchange_strong(lk, true);}
复制代码

unlock

unlock 的实现和 lock 几乎是完全一样的,只是将 true 改成 false,将 false 改成 true(source/cocpp/sync/co_spinlock.cpp)。


void co_spinlock::unlock(){    bool lk = true;    while (!locked__.compare_exchange_strong(lk, false))    {        if (lock_type__ == lock_type::in_coroutine)        {            this_co::yield();        }        else        {            std::this_thread::yield();        }        lk = true;    }}
复制代码

存在的问题

实际上如果是面向用户的话,这样的自旋锁实现是有问题的。因为这个自旋锁没有持有者的信息,也就是说如果线程 a 加了锁,现成 b 是可以解锁的,这样用的话显然就乱套了。因此前面说,这里的自旋锁只是内部使用,是为其他工具提供底层支持,并不对用户开放。只要我们的内部实现保证在同一个线程或者是协程内先加锁后解锁就没有问题。

总结

本文讲解了 cocpp 中自旋锁的实现,非常简单,可以说是简陋了。但是仅为其他同步工具提供底层支持也足够用了。

发布于: 刚刚
用户头像

SkyFire

关注

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

会一点点cpp的苦逼码农

评论

发布
暂无评论
一个cpp协程库的前世今生(十二)自旋锁