单线程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); } } }
注意事项
- 这个实现是单线程安全的,因为所有协程调度都在同一个线程里,不需要考虑多线程下的竞态
lock_guard的语义和标准库完全一致:出作用域自动释放锁,即使发生异常也能保证解锁- 必须通过
co_await获取锁,因为锁的获取可能会挂起当前协程,这是协程场景下的特殊要求
内容的提问来源于stack exchange,提问作者IInspectable
相关产品推荐
相关产品推荐

