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

boost::asio::yield_context工作原理及极简无依赖实现问询

Boost.Asio yield_context 工作原理与极简实现解析

问题背景

阅读Boost.Asio文档时遇到如下示例代码:

void foo(boost::asio::yield_context yield)
{
  size_t n = socket.async_read_some(buffer, yield);

  // ...
}

核心困惑

  • 按asyncio的设计逻辑,async_read_some调用不应阻塞线程;
  • 这意味着需要将函数剩余逻辑注册为回调,待数据就绪后触发执行。

两种猜想及否定理由

猜想1:调用async_read_some时直接运行事件循环

此时调用栈结构为:

...
boost_internal_magic
async_read_some
foo
...
main

由于foo尚未返回,若所有函数都通过yield_context调用,找不到合适的代码点注册回调,逻辑不成立。

猜想2:基于setjmp/longjmp实现上下文跳转

线程仅拥有一个主栈,而asyncio的核心是单线程运行多个并发协程,每个协程需要独立的栈空间,这种方式无法满足多协程栈隔离的需求,逻辑不成立。

核心问题

  1. boost::asio::yield_context 是如何将函数转换为可注册的回调逻辑的?
  2. 具备相同功能的极简无依赖C++代码是怎样的?

工作原理解析

Boost.Asio的yield_context基于栈式协程(Stackful Coroutine)实现,底层依赖boost::coroutine库(C++20后可适配标准协程),核心是通过上下文切换保存当前函数的栈状态,而非传统的显式回调注册:

  1. 当把yield_context传入async_read_some时,yield_context会生成一个隐式回调;
  2. async_read_some会将该回调注册到IO事件循环;
  3. 随后yield_context触发上下文切换,暂停当前foo函数的执行,将栈状态保存到独立的协程对象中,然后返回调用者(事件循环或上层协程);
  4. 当IO事件就绪(数据可读),事件循环调用之前注册的回调,回调内部恢复foo函数的栈状态,让foo从async_read_some的调用点继续执行,并返回读取到的字节数n。

这里的关键是:协程的栈状态被独立存储(不占用线程主栈),因此单线程可以同时存在多个暂停的协程,实现逻辑上的并发。

极简无依赖C++实现示例

以下是基于POSIX标准ucontext库(Windows可替换为Fiber API)的极简版本,模拟yield_context的核心逻辑:

#include <ucontext.h>
#include <cstdio>
#include <vector>
#include <functional>

// 协程上下文结构体,保存栈与执行状态
struct Coroutine {
    ucontext_t ctx;
    char stack[1024 * 64]; // 为协程分配独立栈空间
    std::function<void()> func;
    bool finished = false;
};

// 极简事件循环,存储待处理的IO回调
std::vector<std::function<void()>> pending_events;

void run_events() {
    while (!pending_events.empty()) {
        auto event = pending_events.back();
        pending_events.pop_back();
        event();
    }
}

// 模拟yield_context,负责协程的暂停与恢复
struct YieldContext {
    Coroutine* coro;

    void resume() {
        if (!coro->finished) {
            swapcontext(nullptr, &coro->ctx);
        }
    }
};

// 模拟async_read_some逻辑:注册回调后暂停协程
size_t async_read_some(YieldContext yield) {
    // 注册IO就绪后的回调:恢复协程
    pending_events.push_back([yield]() {
        yield.resume();
    });

    // 暂停当前协程,切换回调用者上下文
    swapcontext(&yield.coro->ctx, nullptr);
    // 协程恢复后返回模拟的读取字节数
    return 5;
}

// 被协程调度的业务函数
void foo(YieldContext yield) {
    size_t n = async_read_some(yield);
    printf("Read %zu bytes\n", n);
}

int main() {
    Coroutine coro;
    getcontext(&coro.ctx);
    // 配置协程栈
    coro.ctx.uc_stack.ss_sp = coro.stack;
    coro.ctx.uc_stack.ss_size = sizeof(coro.stack);
    coro.ctx.uc_link = nullptr;

    // 将foo包装为协程入口函数
    makecontext(&coro.ctx, [&]() {
        foo({&coro});
        coro.finished = true;
    }, 0);

    // 启动协程,首次执行foo
    swapcontext(nullptr, &coro.ctx);

    // 运行事件循环,处理IO就绪事件
    run_events();

    return 0;
}

代码说明

  • 使用ucontext_t实现上下文切换,为每个协程分配独立栈空间,实现栈隔离;
  • YieldContext作为协程与IO操作的交互媒介,负责触发暂停与恢复;
  • async_read_some模拟IO操作的回调注册逻辑,触发协程暂停;
  • 事件循环run_events处理就绪事件,恢复对应的协程执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:40:07