linux下唯一真异步路径是io_uring,需内核5.1+且config_io_uring=y/m,须用o_direct|o_rdonly、4kb对齐缓冲区、ioring_setup_iopoll/sqpoll及正确上下文绑定;windows依赖重叠i/o与iocp,二者机制本质不同,不可强行统一抽象。

Linux 用 io_uring 是唯一真异步路径
Linux 下想绕过线程模拟、实现单线程管理成千上万个文件读请求,只能靠 io_uring。它不是封装,是内核原生接口——5.1+ 内核默认启用,但得确认发行版没阉割:zcat /proc/config.gz | grep CONFIG_IO_URING 输出 y 或 m 才算真支持。
常见踩坑点:
-
io_uring_queue_init必须传IORING_SETUP_IOPOLL(SSD 场景)或IORING_SETUP_SQPOLL(高吞吐),否则退化为普通 syscall,毫无性能优势 - 文件必须用
O_DIRECT | O_RDONLY打开;只用O_RDONLY会静默失败,io_uring_prep_read提交后返回-EINVAL - 缓冲区地址必须 4KB 对齐:用
posix_memalign(&buf, 4096, size),别用new char[size]或栈变量,否则大概率SIGBUS - 每次提交前必须调用
io_uring_sqe_set_data(sqe, req_ptr),其中req_ptr是堆上对象(如std::shared_ptr<read_op></read_op>),completion 回调里全靠这个恢复上下文
Windows 只能靠重叠 I/O 模拟异步语义
Windows 没有内核级文件异步 I/O,但 ReadFile + OVERLAPPED 能做到“调用立即返回 + 完成后通知”,这就是生产环境实际使用的异步模型。
关键约束条件:
- 文件句柄必须用
CreateFile带FILE_FLAG_OVERLAPPED打开,否则ReadFile直接同步执行,哪怕传了OVERLAPPED* -
ReadFile返回FALSE不代表失败,必须立刻调GetLastError()—— 若是ERROR_IO_PENDING,说明已入队,这才是正常流程 -
OVERLAPPED结构里的hEvent可设为NULL,但之后必须用GetQueuedCompletionStatus配合 I/O 完成端口(IOCP),不能靠WaitForSingleObject等事件 - 缓冲区必须堆分配(如
std::vector<char></char>),生命周期要覆盖整个异步周期;栈变量在ReadFile返回后可能已被销毁,导致读到野内存
跨平台抽象层不能假装有统一异步 API
别试图封装一个 async_read_file(path, cb) 让 Linux 和 Windows 表现一致——底层机制完全不同:Linux 的 io_uring 是提交即忘、内核回调;Windows 的重叠 I/O 是用户态轮询/完成端口驱动。强行统一抽象只会掩盖差异、引入隐式线程或错误状态处理。
可行做法是分发层隔离:
- 定义统一的完成回调签名:
void(std::error_code, std::span<const std::byte>)</const> - Linux 实现走
io_uring_wait_cqe+io_uring_cqe_get_data解包上下文 - Windows 实现走 IOCP 线程池 +
GetQueuedCompletionStatus分发 CQE - 共用一套 buffer 管理策略(如 RAII 封装对齐内存)、错误码映射逻辑(把
-EAGAIN映射为std::errc::resource_unavailable_try_again)
std::thread / 线程池方案不是异步,只是“不卡主线程”
用 std::async 或自建线程池包装 read() / ReadFile,本质仍是同步读取:线程在磁盘 I/O 上真实阻塞,只是换了个线程而已。当并发读小文件时,线程数暴涨,上下文切换开销压倒 I/O 本身。
仅在以下场景可接受:
- 目标文件确定在本地 SSD,且数量有限(
- 你明确控制线程数上限(比如固定 4 个工作线程),并用
std::promise+std::future传递结果 - 不追求单线程高并发,只要求 GUI 或网络主线程不被阻塞
- 编译目标包含 macOS —— 它既没有
io_uring,也没有 Windows 那套重叠 I/O,只能靠线程模拟
真正跨平台异步文件读的复杂点不在代码量,而在资源生命周期绑定方式:Linux 要 pin buffer 物理页,Windows 要保活 OVERLAPPED 和句柄,而线程方案只需管好 std::vector 生命周期。选哪条路,取决于你愿不愿意为每种系统写专用 glue code。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











