std::coroutine_handle需通过epoll_data.ptr或kevent.udata绑定i/o多路复用器,注册时存地址、唤醒前检查handle.done(),linux用et模式防重复resume,macos配ev_oneshot;await_suspend返回void表示确定挂起,true/false由逻辑判断,suspend_always语义等价于true;await_ready应预检数据就绪(如fionread)以避免无效挂起;异常必须经promise.set_exception()由协程框架传播,不可在awaiter内捕获exception_ptr。

std::coroutine_handle 怎么和 epoll/kqueue 绑定?
协程本身不调度,只是状态机;真正驱动它的是 I/O 多路复用器。关键不是“封装协程”,而是让 std::coroutine_handle 能在 fd 就绪时被安全唤起。
- 必须把
coroutine_handle存到用户数据区(比如epoll_data.ptr或kevent.udata),不能只存局部变量地址 - 唤起前要检查协程是否已销毁(
handle.done()),否则调用已析构栈帧会崩溃 - Linux 上注意
epoll_wait可能返回多个就绪事件,但一个coroutine_handle只应被 resume 一次,避免重复 resume 导致未定义行为 - macOS 的
kqueue需配合EV_ONESHOT使用,否则事件持续触发,协程反复 resume
// 示例:epoll 中注册读事件并关联 handle
epoll_event ev{};
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = static_cast<void*>(handle.address());
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
await_suspend 返回 void 还是 bool?什么时候用 std::suspend_always?
这不是风格选择,而是语义分水岭:await_suspend 返回 void 表示“立刻挂起,后续靠外部唤醒”;返回 bool 表示“我来决定是否挂起”。
- 如果你已经把
coroutine_handle注册进 epoll,就该返回void—— 挂起是确定的,resume 时机由内核通知决定 - 返回
true表示挂起,false表示不挂起、继续执行(适合快速路径,比如缓冲区有数据可直接读) -
std::suspend_always是个 tag,仅用于await_suspend返回类型为该 struct 时,它等价于返回true,但更明确表达“不犹豫,必挂起”
常见错误:在 await_suspend 里做阻塞 I/O(如 read()),这会让整个 event loop 卡住 —— 协程挂起逻辑必须是非阻塞的。
怎么避免 co_await 一个已就绪的 socket 造成额外调度开销?
TCP socket 的 recv() 在数据到达时可能立即返回,此时协程不该挂起,而应同步完成。
立即学习“C++免费学习笔记(深入)”;
- 在
await_ready中调用ioctl(fd, FIONREAD, &n)(Linux/macOS)或WSAEventSelect(Windows)预检是否有数据 - 注意
FIONREAD对 TCP 是可靠的,但对 UDP 可能受 MTU 和分片影响,返回值未必等于完整报文长度 - 如果
await_ready()返回true,await_resume()必须能立刻返回结果,不能抛异常或触发新挂起 - 不要依赖
SO_RCVBUF大小判断就绪,内核接收缓冲区和应用层是否可读是两回事
性能影响:每次 co_await 都走 await_ready → await_suspend → await_resume 三步,其中两步虚函数调用。预检跳过挂起能省下至少一次上下文切换和 epoll_wait 唤醒延迟。
为什么自定义 awaiter 里不能直接捕获 std::exception_ptr?
C++20 协程的异常传播机制要求:如果 await_resume() 抛异常,它必须由协程框架自动 rethrow 到恢复点;手动捕获 std::exception_ptr 并在 awaiter 内处理,会破坏这个链路。
- 错误做法:在
await_resume()里 try/catch,然后返回错误码 —— 这让调用方无法用try/catch捕获原始异常 - 正确做法:让底层 I/O 函数(如
async_read)在出错时调用promise.set_exception(),由协程运行时保证异常出现在co_await表达式位置 - 容易忽略的一点:如果 awaiter 构造时就失败(比如 fd 无效),必须在
await_ready()或构造函数里抛异常,否则协程甚至不会启动
复杂点在于:网络错误(ECONNRESET、ETIMEDOUT)和系统错误(EBADF)需要不同处理粒度,但协程异常机制不区分层级 —— 所以实际项目中往往得在 await_resume() 里重新包装成领域异常,而不是依赖 errno 原样透出。











