我正在阅读
boost::asio
的文档并遇到了这个示例:
void foo(boost::asio::yield_context yield)
{
size_t n = socket.async_read_some(buffer, yield);
// ...
}
我很困惑:
async_read_some
的调用不会阻塞。我看到
boost::asio
如何实现这个的两个选项:
socket.async_read_some
时,该函数本身会调用事件循环中的一个函数,该函数尝试查找任何准备好处理的事件并尝试调用这些回调。setjmp
/longjmp
魔法为此函数注册了一个回调,并在稍后返回。这两个选项似乎都不起作用:
...
boost_internal_magic
async_read_some
foo
...
main
因为我们还没有从 foo 返回。但是,如果使用
yield_context
调用所有函数,我不知道我们如何实际注册回调,因为没有代码点可以作为函数调用跳转到。
asyncio
的全部要点是在同一线程上运行多个并发“代理”或“协程”或其他东西。每个代理都需要自己的堆栈,所以这似乎也不起作用。boost::asio::yield_context
如何将函数变成可以注册的回调?具有相同功能的最小无依赖 C++ 代码是什么样的?
诀窍在于
yield_context
采用了堆栈协程。协程有自己独立的堆栈,该堆栈在挂起/恢复周期中保留。
其技术实现是在 Boost Context 中。早期版本的 Asio 实际上是在另一个库之上实现的:Boost Coroutine(在底层使用 Boost Context)。
Asio 1.24.0 / Boost 1.80 更改以跳过中间人:
当面向 C++11 及更高版本时,这些函数直接通过 Boost.Context 实现。现有的重载已保留但已弃用。
您可以在该库的文档中阅读 Boost Context 的技术工作原理:https://www.boost.org/doc/libs/1_86_0/libs/context/doc/html/index.html
要详细了解实施的细节,您可以跟踪实施的进展,例如在:
#define BOOST_ASIO_DISABLE_BOOST_COROUTINE 1
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
namespace asio = boost::asio;
using namespace std::chrono_literals;
int main() {
asio::io_context ctx;
spawn(
ctx,
[](asio::yield_context yield) {
asio::steady_timer tim(yield.get_executor(), 3s);
tim.async_wait(yield);
},
asio::detached);
ctx.run();
}
这将引导您到
boost/asio/impl/spawn.hpp
,并向您展示非 Boost.Coroutine 实现实际上使用了 Boost.Context 的 Fiber (BOOST_ASIO_HAS_BOOST_CONTEXT_FIBER
)。
// Spawned thread implementation using Boost.Context's fiber.
class spawned_fiber_thread : public spawned_thread_base
{
public:
typedef boost::context::fiber fiber_type;
spawned_fiber_thread(fiber_type&& caller)
: caller_(static_cast<fiber_type&&>(caller)),
on_suspend_fn_(0),
on_suspend_arg_(0)
{
}
// ...
从这里您可以任意深入地了解实现细节。