4个常见的IO模型——阻塞、非阻塞、多路复用、异步
在Unix网络编程中,史蒂文斯给出了5种IO编程模型,其中最重点、也最常用的是多路复用模型(Multiplexing)。
这5种模型分别为:
- 阻塞式IO
- 非阻塞式IO
- IO多路复用(multiplexing io),基于select/poll/epoll
- 信号驱动式IO SIGIO
- 异步IO(posix aio_abi和libaio)
想要说明清楚这几个模型,一个很好的方式是把网络IO分为两个阶段来理解。第一阶段,从客户端向服务端发送请求开始,到数据从网络传输到达,完全准备好为止。第二阶段,数据从内核空间复制到程序缓存(即用户空间),这个阶段才是应用真正执行recvfrom
系统调用的阶段。在第一阶段,程序要么阻塞在recvfrom
调用上,要么阻塞在select
之类的方法上,或者干其他的事情去了(轮询、异步等)。
有了这个基本认知,我们再来逐个审视这几种模型。
阻塞式I/O模型
阻塞式IO(blocking I/O)是最基本的IO模型,也是日常使用中默认的模式。
如上图,应用发出请求,试图执行recvfrom
系统调用,以获取数据,由于数据还没准备好(也许服务端才开始处理请求),当前请求线程被阻塞,只能傻傻的等待,直到数据可读或者抛出异常。如果是单线程应用,主线程挂起,CPU空置。如果是多线程,当前线程挂起,CPU切换时间片去执行其他线程。
非阻塞式IO模型
一般的Socket
对象都会有一个setblocking(False)
或者ConfigureBlocing(false)
之类的方法,将当前IO线程设置成非阻塞模式。
如图,当socket设置为nonblocking时,在一阶段(数据准备阶段,还记得前面说的二阶段IO么,这里派上了用场)线程会不断的发起recvfrom
轮询,如果还没有准备好数据,会得到一个EWouldBLOCK
错误信号,直到数据准备好之后,开始真正执行recvfrom
拉取数据。轮询会极大的消耗CPU时间,所以这种模型极少用到。
IO多路复用
I/O复用,即I/O multiplexing,这是一种基于select
函数的编程模型,也是最常用的一种。有些人喜欢把它称作异步阻塞模型,我觉得这种叫法很容易让人产生误解,实际上这个模型和所谓的异步I/O没有半毛钱关系。
如上图,在第一阶段,我们可以通过select函数注册多路IO对象。每一个注册的IO对象都会阻塞在select
函数上,直到IO对象的状态发生改变。此时数据已经准备好读/写。调度器遍历这些IO对象,返回准备好读/写的IO对象。紧接着进入第二阶段,开始对准备好的IO对象调用recvfrom
函数。
对于多路复用模型,Java NIO基于select
库实现了调度器Selector
,python的selectors
模块分别提供了基于select
、poll
以及epoll
库的封装对象。
通过以上过程,我们还可以看到,其实IO多路复用跟阻塞IO很相似,基本两个阶段都在阻塞状态。只不过前者第一阶段阻塞在select
函数上,第二阶段阻塞在recvfrom
调用上;而后者全程阻塞在recvfrom
上。而且IO多路复用由于需要注册、遍历IO对象,其实涉及到更多的步骤开销。但是多路复用的优势正在于可以同时对接多个IO对象,结合多线程技术,可以带来很大的灵活性。
异步IO模型
前面提到的三种模型,本质上在真正的IO阶段(第二阶段),都会阻塞。而在异步IO模型中,应用发出数据请求后,不再等待,直接返回。期间线程也不会阻塞。之后由内核处理两个阶段IO,然后给应用发送信号,程序直接获取数据。
基于Unix系统有一个POSIX异步IO库——aio_abi
,以及一个第三方库aiolib
,后者可能更加知名。异步IO模型非常复杂,一般很少见到。另外我也有一些疑惑,python中的asyncio
包提出的协程概念,和这里的异步IO是否为同一件事物。史蒂文斯在这里描述的异步明显需要基于系统内核的相关实现,和asyncio这种单纯的编程概念,大概说的不是一个层级的东西吧。
隐喻
最后还是说一个比喻吧。如果把IO模型比作网购,在你提交订单的一刻到快递员将货物带到你家小区门口期间,可看作一阶段IO。之后小哥给你打电话:“你的快递,快开门”,这相当于数据准备好了,并发送了通知。之后,你跑到门口将快递一件一件搬到家里,这是第二阶段。
阻塞模型相当于你提了订单,就不吃不喝,只等着快递,直到小哥给你打电话,然后吭哧吭哧把快递搬到家,你才开始吃喝、睡觉等。非阻塞模型下,你提交订单后就开始自由行动了。但是仍然会每隔几分钟跑到小区门口看看快递到了没(轮询),一直把自己搞得精疲力尽。直到小哥给你打电话,你把快递搬回家才算消停。多路复用模型下,你同时提交了多个订单,但不再傻傻的朝门口来回跑了,而是紧紧盯着菜鸟裹裹信息(阻塞在select),直到其上显示至少有一个快递到了,你会嗖的跑过去,当然,快递仍然自己搬(recvfrom阻塞)。异步模型下,你雇了一个万能管家吉福斯,你只要下完订单,就不用操心了,他会打点好一切,最后他会把快递按照你们约定的地点、方式,直接送到你的手上。