协程不能直接读磁盘,必须依赖io_uring(Linux)或IOCP(Windows)等真异步I/O引擎;std::fstream、std::filesystem及C++23相关接口均为同步阻塞,co_await包裹无效;跨平台方案如Boost.Asio对文件仍用线程池模拟,非真异步。

协程不能直接读磁盘,必须绕过阻塞系统调用
标准 read()、fread() 或 C++23 std::filesystem 的同步接口在调用时会阻塞线程——哪怕你用 co_await 包裹它,协程依然挂起整个线程。真正非阻塞的磁盘 I/O 在 Linux 上依赖 io_uring(5.1+),Windows 上依赖 IOCP,而 POSIX 没有原生异步文件 API。所以“基于协程的异步文件流”本质是:协程 + 底层异步 I/O 引擎封装。
- 别试图用
std::jthread+co_await包裹std::ifstream::read()——这仍是同步阻塞,只是换了个线程执行 - Linux 下优先选
io_uring:零拷贝、无内核线程切换开销,且支持直接读文件到用户缓冲区 - 跨平台方案(如 Boost.Asio)默认对文件使用线程池模拟异步,实际仍是阻塞调用 + worker thread,不算真异步
用 liburing 封装一个 co_awaitable 文件读取器
以 Linux 为例,最轻量可靠的路径是直接对接 liburing,并提供符合 C++20 协程约定的 awaiter。关键不是“怎么写协程”,而是“怎么把 io_uring_sqe 提交和完成等待变成可挂起操作”。
- 需手动初始化
io_uring实例(io_uring_queue_init(1024, &ring, 0)),并确保其生命周期长于所有协程 - 每个读请求对应一个
io_uring_prep_read(),设置offset、buf、nbytes,然后io_uring_submit() - awaiter 的
await_ready()返回false;await_suspend()不做任何事(因为提交后立即返回,靠 completion queue 通知);await_resume()从io_uring_cqe*取cqe->res(字节数或负错误码) - 注意:文件 fd 必须用
O_DIRECT打开(且 buffer 对齐到 512B),否则io_uring会回退到普通阻塞读
struct read_awaiter {
io_uring* ring_;
int fd_;
void* buf_;
size_t len_;
off_t offset_;
<pre class="brush:php;toolbar:false;">bool await_ready() { return false; }
void await_suspend(std::coroutine_handle h) {}
ssize_t await_resume() {
struct io_uring_cqe* cqe;
io_uring_wait_cqe(ring_, &cqe);
io_uring_cqe_seen(ring_, cqe);
return cqe->res; // >0 成功,<p>};</p>为什么 std::fstream + C++23 std::generator 不行
std::generator<:span std::byte>></:span> 看似适合流式读取,但它本身不带调度能力,也不触发异步 I/O。若你在 generator 的 yield_value 里调用阻塞读,整个协程栈仍被卡住。
-
std::generator是单次前向迭代器,无法暂停等待外部事件(比如io_uring完成) - C++23 标准库目前没有定义任何文件 I/O 的协程化接口,
<filesystem></filesystem>和<fstream></fstream>全部同步 - 某些第三方库(如 cppcoro)提供
file_read_operation,但底层仍是线程池包装read(),不是真异步
实际部署时最容易忽略的三个点
很多 demo 能跑通但线上崩,问题不在协程语法,而在 I/O 层与 OS 的耦合细节。
-
io_uring需要内核支持(5.1+),且发行版 glibc 可能未启用LIBURING_IOURING_ENABLED,编译前先确认pkg-config --modversion liburing - buffer 必须页对齐(
posix_memalign(&buf, 4096, size)),否则O_DIRECT读会失败并返回-EINVAL - 并发读多个 offset 时,不要复用同一个
io_uring_sqe结构体——每个 awaiter 必须独占 sqe,否则提交冲突导致 cqe 返回乱序
协程只是让异步逻辑看起来像同步,真正的非阻塞能力全靠底层 I/O 引擎。写错 buffer 对齐或漏掉 io_uring_cqe_seen(),比协程语法错误更难 debug。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











