阻塞,非阻塞,同步,异步的理解
典型的IO分为两个阶段:
数据的准备:根据系统IO操作的就绪状态,阻塞,非阻塞(从外部向内核缓冲区拷贝数据,应用进程的状态
内核缓冲区上是否有数据可读,数据没有准备好,应用调用recv阻塞住,应用调用函数立即返回非阻塞
socket上没有数据阻塞住,直到有数据
数据读写:根据应用程序和内核的交互方式,同步,异步
业务层面的一个逻辑处理,业务层面也有同步和异步之分
应用程序调用recv函数向内核发送IO请求时,内核缓冲区的数据还没有准备好,此时应用程序阻塞在请求的状态,等待recv函数返回就是“阻塞”。随后当内核缓冲区中的数据准备好以后,应用程序再通过recv函数将数据搬运到用户态就是“同步”。
关于 “阻塞” 的理解:正确
当应用程序调用 recv
函数时,如果内核缓冲区中没有数据(或数据不足),内核会让当前进程进入阻塞状态(暂停执行,让出 CPU 资源),直到内核缓冲区中有足够数据后,才会唤醒进程继续执行 recv
操作。这个 “暂停等待” 的状态,就是阻塞。
简单说:“阻塞” 描述的是进程在等待 IO 事件(数据就绪)时的状态—— 是否会暂停执行、让出 CPU。
关于 “同步” 的理解:正确,但需明确核心
当内核缓冲区数据准备好后,recv
函数会负责把数据从内核态缓冲区拷贝到用户态缓冲区,这个拷贝过程需要应用程序 “主动参与”(通过 recv
调用触发,并且进程会等待拷贝完成后才继续执行)。这种 “IO 操作的完成(包括数据拷贝)需要应用程序主动等待并参与” 的模式,就是同步 IO。
核心点:“同步” 的关键是数据从内核到用户态的拷贝过程,需要应用程序等待并主导完成。recv
是典型的同步 IO 函数,因为它不仅要等数据就绪,还要亲自完成数据拷贝,整个过程需要应用程序阻塞(或轮询)等待。
额外澄清:阻塞 vs 同步的关系
两者不是同一维度的概念,但常被一起讨论:
- 阻塞 / 非阻塞:描述进程在等待 IO 事件(如数据就绪)时的状态(是否暂停执行)。
- 同步 / 异步:描述 IO 操作的完成方式(是否需要应用程序参与数据拷贝,以及是否需要等待整个过程完成)。
recv
在默认情况下是 “阻塞同步 IO”:既会阻塞等数据,又需要应用程序参与拷贝。- 如果用非阻塞模式调用
recv
(通过fcntl
设置),则是 “非阻塞同步 IO”:数据没准备好时不阻塞(直接返回错误),但数据就绪后仍需应用程序通过recv
完成拷贝(同步特性不变)。 - 异步 IO(如 Linux 的
io_uring
或 Windows 的IOCP
)则不同:应用程序发起 IO 请求后就可以做其他事,内核会自己完成 “等数据 + 拷贝数据”,最后通过回调 / 信号通知应用程序 “操作已完成”,全程不需要应用程序等待或参与拷贝 —— 这才是 “异步”。
应用程序调用recv函数向内核发送IO请求时,内核缓冲区的数据还没有准备好,此时应用程序判断recv函数的返回值,不阻塞在请求的状态,就是“非阻塞”。随后当内核缓冲区中的数据准备好以后,内核通过应用程序注册的函数将数据拷贝到用户态通知应用程序拿走,这就是“异步”。
关于 “非阻塞” 的理解:
当应用程序以非阻塞模式调用 recv
时,如果内核缓冲区没有数据,recv
不会让进程进入阻塞状态(不会 “卡住等待”),而是立即返回一个错误码(比如 Linux 中的 EAGAIN
或 EWOULDBLOCK
),告诉应用程序 “当前没数据,你先去忙别的”。
这种 “不等数据就绪,直接返回状态” 的模式,就是非阻塞。它的核心是:进程在 IO 操作未就绪时,不会暂停执行,而是可以继续处理其他任务(比如隔一会儿再重试 recv
,或处理其他逻辑)。
关于 “异步” 的理解:完全正确
异步 IO 的核心是 “内核全程包办,应用程序彻底不参与等待和拷贝”:
- 应用程序通过特定接口(如
aio_read
或io_uring
)发起 IO 请求,并注册一个回调函数(告诉内核 “数据准备好并拷贝完成后,就调用这个函数通知我”); - 发起请求后,应用程序可以立即去做其他事,完全不需要等待(既不阻塞,也不用轮询);
- 当内核缓冲区数据就绪后,内核会主动将数据从内核态拷贝到用户态缓冲区,然后通过回调函数(或信号)通知应用程序 “数据已经备好,你可以直接用了”。
整个过程中,应用程序既不用等待数据就绪,也不用参与数据拷贝,只是在最后接收 “完成通知”—— 这就是异步的本质。
一句话总结区别:
- 非阻塞:IO 未就绪时不阻塞,但数据就绪后仍需应用程序主动调用
recv
完成拷贝(同步特性不变); - 异步:应用程序发起请求后彻底 “不管”,内核自己搞定 “等数据 + 拷贝数据”,最后通知结果(全程无需应用程序参与等待或拷贝)。