You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单线程C++协程中如何保护代码段避免并发执行?(C++17+Coroutine TS)

单线程协程中实现类似std::lock_guard的并发防护需求

我现在需要在单线程调度的协程里保护一段代码,避免多个协程并发执行这段逻辑。在多线程环境里用std::lock_guard就搞定了,但我的协程都是单线程调用的,这个方案完全不适用。

我想要实现的逻辑大概是这样的(伪代码):

future<http_response> send_req_async(http_request req) {
    while (true) {
        // 尝试发送HTTP请求
        auto const& access_token{ token_store::access_token() };
        auto const response{ co_await impl::send_req_async(req, access_token) };

        if (response.status_code() == http_code::ok) {
            co_return response;
        }

        // 尝试刷新access token
        if (response.status_code() == http_code::unauthorized) {
            // 下面这段代码需要被防护,避免并发执行
            // 要是在多线程里我会这么写:
            // lock_guard<mutex> guard(refresh_token_mutex);

            if (access_token != token_store::access_token()) {
                continue;
            }

            auto const& token{ co_await refresh_token(token_store::refresh_token()) };
            token_store::save_access_token(token);
            // 结束需要防护的代码段
        }
    }
}

这段代码允许同时发起多个请求,但我希望只有一个协程能执行刷新token的操作,其他协程在这段逻辑执行时挂起,等操作完成后自动恢复——就和多线程里std::lock_guard的语义一模一样。

想问问:C++标准库或者Coroutine TS里有没有现成的简洁实现?还是说必须自己写?

注:我用的是Visual Studio 2017 15.7.2,完全支持C++17和Coroutine TS。


好问题!在单线程协程场景下,C++标准库(包括Coroutine TS)确实没有提供直接对应std::lock_guard的现成组件,不过我们可以基于协程的挂起/唤醒机制,自己实现一个轻量的协程互斥锁,完美匹配你想要的语义。

核心思路

因为是单线程调度,我们不需要真正的系统级互斥量——只需要跟踪两个状态:

  • 当前是否有协程持有"锁"
  • 等待获取锁的协程队列

当一个协程尝试获取锁时:

  • 如果锁未被持有,直接进入临界区
  • 如果锁已被持有,就将当前协程挂起,加入等待队列
    当持有锁的协程离开临界区时,依次唤醒队列里的等待协程。

实现代码

这里给出一个适配Coroutine TS的实现:

#include <coroutine>
#include <queue>

class coroutine_mutex {
public:
    class lock_guard {
    public:
        explicit lock_guard(coroutine_mutex& mutex) : m_mutex(mutex) {
            // 这里不需要co_await,因为lock_guard依赖提前获取到的锁
        }

        ~lock_guard() {
            m_mutex.unlock();
        }

        // 禁用拷贝和移动
        lock_guard(const lock_guard&) = delete;
        lock_guard& operator=(const lock_guard&) = delete;
        lock_guard(lock_guard&&) = delete;
        lock_guard& operator=(lock_guard&&) = delete;

    private:
        coroutine_mutex& m_mutex;
    };

    // 自定义awaiter,用于co_await获取锁
    class awaiter {
    public:
        explicit awaiter(coroutine_mutex& mutex) : m_mutex(mutex) {}

        bool await_ready() const noexcept {
            return !m_mutex.m_locked;
        }

        bool await_suspend(std::coroutine_handle<> handle) noexcept {
            m_mutex.m_wait_queue.push(handle);
            return true; // 挂起当前协程
        }

        void await_resume() noexcept {
            m_mutex.m_locked = true; // 唤醒后标记锁已持有
        }

    private:
        coroutine_mutex& m_mutex;
    };

    awaiter lock() noexcept {
        return awaiter{*this};
    }

private:
    friend class lock_guard;

    void unlock() noexcept {
        m_locked = false;
        if (!m_wait_queue.empty()) {
            auto handle = m_wait_queue.front();
            m_wait_queue.pop();
            handle.resume(); // 唤醒第一个等待的协程
        }
    }

    bool m_locked = false;
    std::queue<std::coroutine_handle<>> m_wait_queue;
};

集成到你的代码里

只需要在临界区前通过co_await获取锁,再用lock_guard保证自动解锁即可,完美贴合你原来的代码逻辑:

// 全局或者类内静态的协程互斥锁
coroutine_mutex refresh_token_mutex;

future<http_response> send_req_async(http_request req) {
    while (true) {
        auto const& access_token{ token_store::access_token() };
        auto const response{ co_await impl::send_req_async(req, access_token) };

        if (response.status_code() == http_code::ok) {
            co_return response;
        }

        if (response.status_code() == http_code::unauthorized) {
            // 用协程锁防护临界区:先co_await获取锁,再用lock_guard自动管理生命周期
            co_await refresh_token_mutex.lock();
            coroutine_mutex::lock_guard guard(refresh_token_mutex);

            if (access_token != token_store::access_token()) {
                continue;
            }

            auto const& token{ co_await refresh_token(token_store::refresh_token()) };
            token_store::save_access_token(token);
        }
    }
}

注意事项

  1. 这个实现是单线程安全的,因为所有协程调度都在同一个线程里,不需要考虑多线程下的竞态
  2. lock_guard的语义和标准库完全一致:出作用域自动释放锁,即使发生异常也能保证解锁
  3. 必须通过co_await获取锁,因为锁的获取可能会挂起当前协程,这是协程场景下的特殊要求

内容的提问来源于stack exchange,提问作者IInspectable

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:52:03