一、引言
简单梳理一下 IO 操作中阻塞、非阻塞、同步、异步等概念。
典型的一次 IO 都有两个阶段:数据就绪、数据读写。
大部分 IO 函数都会有内核缓冲区和用户缓冲区。当内核缓存区中有数据可以读或者有空间可以写就是数据就绪阶段,然后就可以进行数据读写。
二、阻塞与非阻塞
阻塞和非阻塞区别在于调用函数的线程是否需要等待结果。
阻塞:当一个线程调用阻塞 IO 函数时,该线程就会进入等待状态,直到该 IO 操作完成才返回结果。
非阻塞:当一个线程调用非阻塞 IO 函数时,该函数会立刻返回结果,线程可以继续执行下面的程序,但是 IO 操作在后台进行。可以通过回调函数(异步)或者轮询(同步)来查询 IO 的结果。
三、同步与异步
同步和异步是 IO 结果的获取方式
同步:调用者复制整个 IO 过程,包括检查 IO 状态,等待数据就绪,获取结果(可以使用阻塞函数或者非阻塞函数),相当于调用者自己搬运数据。
异步:调用者调用函数的过程相当于通知一声之后,操作系统(或者其他硬件)会在数据就绪之后直接把数据放到程序中,然后通知线程或者执行回调函数。
同步非阻塞:用户进程向内核线程发起 IO 请求,如果内核空间有数据则内核线程立刻将内核缓冲区中的数据发送给用户进程。如果内核空间没有数据,则用户进程立即返回,不做等待。
异步阻塞:异步阻塞很罕见,只有极其特殊的情况下才会使用。
五、其他
Linux 异步 IO 有 glibc aio(aio_ *)
和 kernel native aio(io_ *)
,但是他们的实现并不是很完善。而 epoll 是一种 IO 多路复用方法,它算是一种模拟异步 IO 的方法,但是本质上还是同步 IO,它只是在检测到有可以操作(即 IO 就绪)的 IO 时“告知”线程哪些描述符已就位,最终的数据搬运还是需要线程调用 IO 函数进行。
目前 Linux 服务器编程使用的最多的还是 multiple reactors + thread pool
。
Windows 上的 IOCP 是异步 IO 模型。