Lab4d 实验解析

tinyCoroLab4d 验解析

⚠️tinyCoroLab 的实验强烈推荐实验者独自完成而非直接翻阅实验解析,否则这与读完题直接翻看参考答案无太大区别,实验解析仅供实验者参考。

本节将会以 tinyCoroLab 的官方实现tinyCoroarrow-up-right为例,为大家分析并完成 lab4d,请实验者预先下载 tinyCoro 的代码到本地。

git clone https://github.com/sakurs2/tinyCoro

打开include/coro/comp/mutex.hpparrow-up-rightsrc/comp/mutex.cpparrow-up-right并大致浏览代码结构。

📖lab4d 任务参考实现

🧑‍💻Task #1 - 实现 mutex

mutex 的实现难点在于每次unlock只会恢复最多一个协程的运行,对于频繁lockunlock的场景必须保证协程调度的正确性。

为了讲解方便,我先给出 mutex 实现的函数声明形式并给出注释讲解,这样更直观。

class mutex
{
public:
    struct mutex_awaiter
    {
        mutex_awaiter(context& ctx, mutex& mtx) noexcept : m_ctx(ctx), m_mtx(mtx) {}

        constexpr auto await_ready() noexcept -> bool { return false; }

        auto await_suspend(std::coroutine_handle<> handle) noexcept -> bool;

        auto await_resume() noexcept -> void;

        // awaiter 将自身注册到 mutex 的 awaiter 链表中,注册成功即陷入 suspend 状态
        auto register_lock() noexcept -> bool;

        // 对于恢复协程过程的封装,声明为虚函数是因为后续需要
        virtual auto resume() noexcept -> void;

        context&                m_ctx; // 绑定的 context
        mutex&                  m_mtx; // 绑定的 mutex
        mutex_awaiter*          m_next{nullptr}; // mutex 协程链表的 next 指针
        std::coroutine_handle<> m_await_coro{nullptr}; // 待 resume 的协程
    };

    // 用于 mutex.lock_guard 的返回值,其逻辑与 mutex_awaiter 相同因此继承了 mutex_awaiter,
    // 但重写了 await_resume 用于返回 lock_guard
    struct mutex_guard_awaiter : public mutex_awaiter
    {
        using guard_type = detail::lock_guard<mutex>;
        using mutex_awaiter::mutex_awaiter;

        auto await_resume() noexcept -> guard_type
        {
            mutex_awaiter::await_resume(); // 保留原有的逻辑
            return guard_type(m_mtx);
        }
    };

public:
    mutex() noexcept : m_state(nolocked), m_resume_list_head(nullptr) {}
    ~mutex() noexcept { assert(m_state.load(std::memory_order_acquire) == mutex::nolocked); }

    auto try_lock() noexcept -> bool;

    auto lock() noexcept -> mutex_awaiter;

    auto unlock() noexcept -> void;

    auto lock_guard() noexcept -> mutex_guard_awaiter;

private:
    friend mutex_awaiter;

    // 常量 1 表示当前锁处于未加锁状态,常亮 0 表示锁处于加锁但没有协程在等待获取锁
    // 注意 0 和 1 不是随便设置的,原因稍后讲解
    inline static awaiter_ptr nolocked          = reinterpret_cast<awaiter_ptr>(1);
    inline static awaiter_ptr locked_no_waiting = 0; // nullptr
    std::atomic<awaiter_ptr>  m_state; // 与 event 类似,同样也是挂载 suspend awaiter 的链表头
    awaiter_ptr               m_resume_list_head; // 用于存储待恢复的 suspend awaiter,作用稍后讲解
};

然后我们对重点函数的实现进行分析,首先是mutex_awaiter中关于协程调度的函数:

然后是mutex_awaiter挂载 awaiter 的逻辑:

然后是mutex_awaiter用于恢复协程执行的逻辑:

对于 mutex 的 lock 部分的接口其实现如下:

mutex 的unlock实现相较复杂:

注意上述unlock中对挂载 awaiter 的链表进行翻转操作,因此 mutex 的unlock操作遵从 FIFO 调度逻辑。

综合来看,对 mutex 的实现主要考察实验者对原子变量的使用。

实验总结

  • 通过实现 mutex 加深了对原子变量的理解,合理的使用原子变量来构建无锁并发数据结构可以大大提高多线程编程的效率

Last updated