我正在阅读文档boost::asio
并发现了这个例子:
void foo(boost::asio::yield_context yield)
{
size_t n = socket.async_read_some(buffer, yield);
// ...
}
我很困惑:
- 如果我正确理解了 asyncio,那么对 asyncio 的调用
async_read_some
就不会阻塞。 - 这意味着当数据真正准备好读取时,此调用将注册函数的其余部分以运行。
我看到两种boost::asio
实现方式:
- 当
socket.async_read_some
被调用时,此函数本身会调用事件循环中的一个函数,该函数尝试查找任何已准备好处理的事件并尝试调用这些回调。 - 一些
setjmp
/longjmp
magic 为该函数注册一个回调,并在稍后返回。
这两个选项似乎都不起作用:
- 调用堆栈看起来类似于:
...
boost_internal_magic
async_read_some
foo
...
main
因为我们还没有从 foo 返回。但是如果所有函数都使用 调用,yield_context
我不知道我们如何实际注册回调,因为没有代码点可以跳转到函数调用。
- 只有一个堆栈,但其全部目的
asyncio
是在同一线程上运行多个并发“代理”或“协同程序”或其他东西。每个代理都需要自己的堆栈,因此这似乎也行不通。
如何boost::asio::yield_context
将函数转换为可注册的回调?具有相同功能的最小无依赖 C++ 代码是什么样的?
诀窍是
yield_context
使用stackful 协程。协程有自己独立的堆栈,在挂起/恢复周期中保留。这一技术实现是在 Boost Context 中。Asio 的早期版本实际上是在另一个库之上实现的:Boost Coroutine(其底层使用了 Boost Context)。
Asio 1.24.0 / Boost 1.80已更改为跳过中间人:
进一步阅读
您可以在该库的文档中阅读有关 Boost Context 的技术工作原理:https://www.boost.org/doc/libs/1_86_0/libs/context/doc/html/index.html
要详细了解实施的细节,您可以跟踪实施的进展情况,例如:
在 Coliru 上直播
这将引导您进入
boost/asio/impl/spawn.hpp
,并向您展示非 Boost.Coroutine 实现实际上使用了 Boost.Context 的纤程(BOOST_ASIO_HAS_BOOST_CONTEXT_FIBER
)。从这里,您可以任意深入地跟踪实现细节,直接进入各种汇编代码例程,以帮助进行特定于 CPU 的上下文切换:https://github.com/boostorg/context/tree/develop/src/asm