异步(Asynchronous) 和 非阻塞(Non-blocking)
一、阻塞 vs 非阻塞(Blocking vs Non-blocking)
核心区别: 发起 I/O 调用时,线程是否会被挂起等待结果。
-
阻塞 I/O (Blocking I/O)
-
含义:当线程发起 I/O 操作(如
read()读取网络数据),若数据未就绪,线程会被操作系统挂起(睡眠),直到数据准备好并被内核拷贝到用户空间后,线程才被唤醒继续执行。 -
比喻:你去银行柜台办业务,柜员说“需要后台处理,你在这等着”。你只能干等(线程阻塞),直到柜员处理完把结果给你。
-
代码表现:
read()函数不返回,线程卡在这一行。 -
问题:高并发时,每个连接需一个线程,资源消耗大。
-
-
非阻塞 I/O (Non-blocking I/O)
-
含义:当线程发起 I/O 操作,若数据未就绪,函数立即返回一个错误(如
EAGAIN),线程不会被挂起,可以继续执行其他任务。 -
如何实现:通过
fcntl(fd, F_SETFL, O_NONBLOCK)将文件描述符设为非阻塞模式。 -
比喻:柜员说“后台在处理,你可以先去干别的,但需要你自己时不时回来问是否完成”。
-
代码表现:调用
read()可能返回-1且errno == EAGAIN,线程继续执行后续逻辑。 -
优点:线程不被阻塞,可同时处理多个 I/O。
-
缺点:需主动轮询检查状态,浪费 CPU。
-
二、同步 vs 异步(Synchronous vs Asynchronous)
核心区别: I/O 操作的完成结果如何通知调用者。
-
同步 I/O (Synchronous I/O)
-
含义:线程主动等待并获取结果。包括两类:
-
阻塞型同步:线程挂起等待操作完成(如默认的
read())。 -
非阻塞型同步:线程轮询检查状态(如非阻塞
read()+ 循环重试)。
-
-
本质:I/O 操作的完成仍需线程主动参与(等待或轮询)。
-
比喻:无论是干等(阻塞)还是反复询问(非阻塞),最终都是你自己拿到结果。
-
技术代表:
read(),write(),select(),poll(),epoll_wait() + 非阻塞 read()。
-
-
异步 I/O (Asynchronous I/O, AIO)
-
含义:线程发起 I/O 操作后立即返回,内核完成整个操作(包括数据拷贝)后,通过回调、信号或事件通知线程结果。
-
本质:内核完全接管 I/O 操作,线程无需关心过程。
-
比喻:柜员说“留下地址,办完我快递给你”。你直接回家,收到包裹即完成。
-
代码表现:
// Linux 原生 AIO 示例 struct iocb cb = {0}; cb.aio_fildes = fd; // 文件描述符 cb.aio_buf = buffer; // 用户缓冲区 cb.aio_nbytes = size; // 数据大小 cb.aio_lio_opcode = IOCB_CMD_PREAD; // 读操作 io_submit(ctx, 1, &cb); // 提交请求(立即返回) // 内核完成后通过回调或 io_getevents() 通知 -
优点:线程完全自由,无轮询开销。
-
缺点:编程复杂,操作系统支持不完善(如 Linux 原生 AIO 对网络 I/O 支持有限)。
-
三、关键区别总结
| 特性 | 非阻塞 I/O | 异步 I/O |
|---|---|---|
| 关注点 | 调用是否立即返回 | 结果如何通知 |
| 调用行为 | 立即返回(成功/错误) | 立即返回(仅表示提交成功) |
| 数据就绪时 | 需线程主动调用 read/write |
内核自动拷贝数据到用户缓冲区 |
| 完成通知 | 无通知,需线程轮询或事件触发 | 回调、信号、事件通知 |
| 线程参与 | 需线程执行实际 I/O 操作 | 线程完全不参与 I/O 操作过程 |
| 典型代表 | read(fd, buf, size) + O_NONBLOCK |
Linux io_uring / Windows IOCP |
🔥 重要认知:
epoll本质是同步非阻塞模型!
epoll_wait()返回时,仅告诉你哪些 fd 可进行不阻塞的 I/O 操作(数据在内核缓冲区就绪)。你仍需调用非阻塞的
read()/write()将数据从内核空间拷贝到用户空间(这个拷贝过程是同步的)。
四、“异步非阻塞”组合的含义
当同时使用两种技术时:
-
非阻塞:发起 I/O 调用不阻塞线程(立即返回)。
-
异步:内核完成整个 I/O 后主动通知结果(无需线程轮询)。
典型场景:
-
现代高性能框架(如 Java NIO + AIO、Node.js libuv)通过 事件循环 + 线程池 模拟异步:
-
主线程用
epoll管理 I/O 事件(同步非阻塞)。 -
将耗时操作(文件 I/O、数据库)提交给线程池,主线程通过回调获知结果(异步)。
-
五、一张图理解四种 I/O 模型
-
阻塞 I/O:全程等待。
-
非阻塞 I/O:轮询直到数据就绪。
-
I/O 多路复用(
epoll):批量监控 fd,就绪后同步拷贝数据。 -
异步 I/O:提交请求后完全不管,内核完成所有步骤后通知。
总结
-
非阻塞:解决 “调用是否卡住线程” 问题(立即返回)。
-
异步:解决 “操作完成后如何告知” 问题(内核主动通知)。
-
epoll是同步非阻塞模型:它高效管理 fd 状态,但数据拷贝仍需用户线程参与。 -
真正的异步 I/O:内核全程负责 I/O 操作(包括数据拷贝),用户线程彻底解放。
实际开发中,常将 非阻塞 I/O + I/O 多路复用(如 epoll) + 事件循环 组合成高并发架构,在性能和复杂度间取得平衡。而真正的异步 I/O(如 io_uring)是未来发展方向。

浙公网安备 33010602011771号