C++20协程中基于ASIO Strand安全访问共享资源的标准实现方式?
ASIO Strand + C++20协程:安全访问共享资源的标准实现
场景与需求
我们需要保护线程不安全的共享资源(如内存池、非线程安全API),通过ASIO Strand串行化所有资源访问操作以保证线程安全。同时基于C++20协程开发,期望通过co_await挂起协程,切换到指定Strand执行资源操作,完成后自动切回协程的原生执行器继续后续逻辑。
排除的方案
- 禁用"dispatch两次"的技巧:该方式语法繁琐且存在潜在竞态,示例代码如下:
auto s1 = bind_executor(strand, asio::deferred); co_await asio::dispatch(s1); // 访问共享资源 co_await asio::dispatch(asio::deferred);
- 拒绝包装为
asio::awaitable:避免因use_awaitable引入不必要的协程帧分配。
当前临时方案
已实现一个简易版本(存在未使用concepts、不转发参数、不支持返回值等缺陷),核心逻辑可满足"切换到Strand执行操作后切回原执行器"的需求。
疑问
该场景下的标准实现方式是什么?作为ASIO新手,相信此类需求已有成熟解决方案,但相关资料较少,想确认是否遗漏了ASIO内置的自由函数,或是必须每次通过co_await绑定到Strand的延迟操作?
标准实现:自定义Awaiter
最贴合需求的标准做法是实现自定义awaiter,直接控制执行器切换逻辑——既避免额外协程帧开销,又简化调用语法。
基础版:手动切换执行器
先实现两个基础awaiter,分别负责"切换到Strand"和"切回原执行器":
#include <asio.hpp> #include <coroutine> // 切换到指定Strand的awaiter template <typename Executor> struct switch_to_strand_awaiter { explicit switch_to_strand_awaiter(const Executor& ex) : target_exec_(ex) {} bool await_ready() const noexcept { // 当前已在目标Strand上则无需切换 return target_exec_.running_in_this_thread(); } void await_suspend(std::coroutine_handle<> h) const { // 调度到目标Strand后恢复协程 asio::dispatch(target_exec_, [h]() { h.resume(); }); } void await_resume() const noexcept {} private: Executor target_exec_; }; // 切回原执行器的awaiter template <typename Executor> struct switch_back_awaiter { explicit switch_back_awaiter(const Executor& ex) : original_exec_(ex) {} bool await_ready() const noexcept { return original_exec_.running_in_this_thread(); } void await_suspend(std::coroutine_handle<> h) const { asio::dispatch(original_exec_, [h]() { h.resume(); }); } void await_resume() const noexcept {} private: Executor original_exec_; }; // 辅助函数简化创建 template <typename Executor> auto switch_to_strand(const Executor& ex) { return switch_to_strand_awaiter<Executor>(ex); }
使用方式
asio::io_context ioctx; auto strand = asio::make_strand(ioctx); asio::awaitable<void> example_coroutine() { // 保存当前执行器 auto original_exec = co_await asio::this_coro::executor; // 切换到Strand执行资源操作 co_await switch_to_strand(strand); shared_resource->unsafe_operation(); // 此时在Strand串行执行 // 切回原执行器继续逻辑 co_await switch_back_awaiter(original_exec); do_other_work(); }
进阶版:封装自动切换的高阶函数
如果希望更简洁的语法,可以封装一个高阶函数,自动处理"切换到Strand执行操作→切回原执行器"的完整流程,同时支持返回值:
template <typename Executor, typename Func> auto run_on_strand(const Executor& strand, Func&& func) { struct awaiter { Executor strand_; Func func_; std::invoke_result_t<Func> result_; awaiter(const Executor& s, Func&& f) : strand_(s), func_(std::forward<Func>(f)) {} bool await_ready() const noexcept { return strand_.running_in_this_thread(); } template <typename Promise> void await_suspend(std::coroutine_handle<Promise> h) { auto original_exec = h.promise().executor(); // 调度到Strand执行操作,完成后切回原执行器恢复协程 asio::dispatch(strand_, [this, h, original_exec]() { result_ = std::invoke(std::move(func_)); asio::dispatch(original_exec, [h]() { h.resume(); }); }); } decltype(auto) await_resume() { return std::forward<decltype(result_)>(result_); } }; return awaiter(strand, std::forward<Func>(func)); }
使用方式
asio::awaitable<void> example_coroutine() { // 自动切换到Strand执行操作,完成后切回原执行器 auto resource_result = co_await run_on_strand(strand, []() { return shared_resource->compute_value(); }); // 在原执行器处理结果 process_result(resource_result); }
方案优势
- 无额外协程帧:自定义awaiter直接操作协程句柄,无需依赖
use_awaitable创建嵌套协程。 - 清晰的执行流:手动切换版本明确控制执行器切换时机,封装版本则简化重复逻辑。
- 符合ASIO设计:完全基于ASIO的执行器调度机制,保证线程安全的同时保留协程的异步语义。
内容的提问来源于stack exchange,提问作者nickelpro
相关产品推荐
相关产品推荐

