协程调度器必须自行管理awaiter生命周期,避免栈上awaiter被销毁后调度器仍调用await_resume导致崩溃;I/O操作的awaiter需由promise拥有或用shared_ptr管理;高精度定时器须用timerfd或WaitableTimer而非sleep;单线程用epoll/kqueue/IOCP,多线程需独立I/O实例;取消时须分别处理I/O与定时器资源。

协程任务调度器必须自己管理 awaiter 生命周期
直接用 std::coroutine_handle 调度裸协程很容易崩溃——因为协程挂起后,如果其栈帧(含局部 awaiter)被销毁,而调度器还在尝试调用 await_resume(),就会触发未定义行为。常见表现是 segmentation fault 或随机 crash。
正确做法是:所有可挂起的 I/O 操作(如 async_read、async_write)返回的 awaiter 必须被显式拥有,通常封装进一个 task 或 promise_type 的成员变量中,并确保其生命周期覆盖到协程彻底结束(包括被取消或异常退出)。例如:
struct io_awaiter {
// ...
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<promise_type> h) noexcept {
// 把 this 挂到 epoll/kqueue/IOCP 队列上
// 注意:this 必须是堆分配或绑定在 promise 上,不能是栈变量
}
int await_resume() noexcept { return result_; }
};
-
io_awaiter实例必须由promise_type拥有(如作为成员),或通过std::shared_ptr管理 - 不要在 lambda 里捕获局部
awaiter并传给await_suspend - Linux 下若用
io_uring,需注意IORING_SETUP_IOPOLL模式下await_suspend可能同步返回,但await_resume仍必须可重入
高精度定时器不能依赖 std::chrono::steady_clock::now() 做轮询
在调度循环里反复调用 steady_clock::now() 查到期任务,延迟会严重劣化——尤其在空闲时 CPU 占用飙升,且最小分辨率达不到微秒级(典型实现是 1–15ms)。真实场景下,你会看到定时任务实际偏移 20ms+,甚至丢弃短间隔(如 500μs)任务。
可行方案只有两个:系统级定时器 + 事件驱动,或用户态高精度 sleep(仅限 Linux):
立即学习“C++免费学习笔记(深入)”;
- Linux 推荐
timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK),然后把 fd 加入 epoll;每次写入新超时值会触发可读事件 - Windows 用
CreateWaitableTimerEx+WaitForMultipleObjectsEx,避免 Sleep 的粒度缺陷 - 绝对不要用
std::this_thread::sleep_until实现“等待下一个定时点”,它底层仍是粗粒度系统 sleep - 定时器回调触发的协程恢复,必须走同一线程的调度队列,否则跨线程 resume 需额外同步开销
调度器线程模型决定 I/O 多路复用选型
单线程调度器和多线程调度器对底层 I/O 多路复用的要求完全不同,选错会导致性能断崖下跌或死锁。
- 单线程(推荐初版):用
epoll(Linux)、kqueue(macOS/BSD)、IOCP(Windows)——它们天然支持“一个 loop 多个 fd”,且无锁操作 - 多线程 worker(每个线程独立 loop):Linux 下慎用
epoll_wait在多个线程里同时等待同一 epoll fd(内核不保证安全);应为每个线程创建独立epoll,并通过 channel 或 ring buffer 分发新连接 - 混合模型(如主线程 accept + worker 线程处理):accept 线程需用
SO_REUSEPORT避免惊群,且新 socket 必须用epoll_ctl(EPOLL_CTL_ADD)显式加入对应 worker 的 epoll - 所有平台都避免用
select()——它最大 fd 数受限,且每次调用都要拷贝整个 fd_set
取消协程时必须区分 I/O 取消与定时器取消
协程被取消时,co_await 的不同 awaiter 对取消的响应方式差异极大,统一处理会漏掉资源泄漏或状态不一致。
- I/O awaiter(如
async_read):调用cancel()后,内核可能仍在传输数据;需检查返回值是否为operation_aborted,并确保缓冲区不再被访问 - 定时器 awaiter:
timerfd_settime可设为 0 来立即取消,但需注意已触发但尚未 resume 的定时回调仍会执行——应在await_resume里加 flag 判断是否已被 cancel - Promise 层需实现
cancel()方法,在其中调用所有 pending awaiter 的清理逻辑,并设置destroy()标志防止重复销毁 - 切勿在
await_suspend中抛异常来模拟取消——这会跳过final_suspend,导致 promise 内存泄漏
协程调度器最难的部分不在语法糖,而在所有异步原语的生命周期交叉点:I/O 句柄、定时器 fd、awaiter 对象、promise 内存、调度队列节点,任何一个没对齐,崩溃就发生在最不可预测的时刻。


















