要问技术多NB,请问IO模型知多少?
1. 引言
同步异步I/O,阻塞非阻塞I/O是程序员老生常谈的话题了,也是自己一直以来懵懵懂懂的一个话题。比如:何为同步异步?何为阻塞与非阻塞?二者的区别在哪里?阻塞在c V # 6 P !何处?为什么会有多种IO模型,分别用来解决问题?常用的框架采用的是何种I/O模型?各种IO模型的5 Y g ! a n优劣势在哪里,适用于何种应用场景?
简而言之,对于I/O的认知,不能仅仅停留在字面上认识,了解内部玄机) ( d ^,才能深刻理解I/O,才能看清I/O相关问题的本质。
2. I/O 的定义
I/O 的全称! C w +是Input/Output。虽常谈及I/O,但想必你也一时不能给X 2 f p 5出一个完整的定义。搜G D 3 o = d ^ L索了谷歌,发现也尽是些冗长的论述。要想理清I/O这个概念,我们需要从不同的视角去理解它。
2.1. 计算机视角
冯•诺伊曼计算机的基本思想中有提到计算机硬件组成应为五大部分:控制器,运算器,存储器,输入和输出。其中输入是F ) X A p指将数据输入到计算机的设备,比如键盘鼠标;输出是指从计算机中获取数据的设备,比如显示器;以及既是输入又是输出设备,硬盘N G 6 E,网卡等。
用户通过操作系统才能完成对计算机的操作。计算机启动时,第一个启动的程序是操作系统的内核,它将负责计算机的资源管理和进程的调度。换句话说:操作系统负责从输入设备读取数据并将数据写入到输出设备。
所以I/O之于计算机,有两层意思:
- I/O设备
- 对I/O设备的数据读写
对于一次I/O操作,必然涉及2个参与方,一个输入端,一个输0 ! a 2 R - . (出端N - H,而又B % w J C S } ] E根据参与双方的设备类型,我们又可以分为磁盘I/O,网络I/O(一次网络的请求响应,网卡)等。
2.2. 程序视角
应用程序作为一个文件保存在磁盘中,只有加载到内存q p q到成为一个进程才能运行。应用程序运行在计算机内存中,必然会t D L涉及到数据交{ c 2 u换,比如d 2 u d读写磁盘文件,访问数据库,调用远程API等等。但我们编写的程序并不能像操作系统内核一样直接进行I/O操作。
因为为了确保操作系统的安全稳定运行,操作系统启动后,将会开启/ ] H J # _ o保护模式:将内存分为内核空间(内核对应进程所在内存空间)和用户空间,进行内存隔离。我们构建的程序将运行在用户空间,用户空间无法操作内核空间,也就意味着用户空间的程序不能直接访问由内核管理的I/O,比如:硬盘、网卡等。
但操作系统向外提供API,其由各种类型的系统调用(Systemx f G A 7 o e 字。
4. IO 模型之非阻塞Ib y 0 N Y S ! a //O| / / t(NIO)
那解决方案自然也容易想到,将阻塞变为非阻塞,那就是用户进+ ` N ^ N 0 / /程在发起系统调用时指定为非阻塞,内核接收到请求后,就会立即返回,然后用户进程通过? l 5轮询的方式来拉取处理结果。也就是如下图所示:
应用程序中进程在发起IO调用后至内核执行IO操作返回结果之前,若发起系统调用的线程不会等待而是立即返回,则此次IO操作为非阻塞IO模型。非阻塞IO简称NIO,Non-Blocking IO。
然而,非阻塞IO虽然相对于阻塞IO大I j M r幅提升了性能,但依旧不是完美的解决方案D i #,其依然存在性能问题,也就是频繁的轮询导致频繁的系统调用,会耗费大量的CPU资源。比如当并发很高时,假设有1000个并发,那么单位时间循环内将会有1000次系统调用去轮询执行结果,而实际上可能只有2个请求结果执行完毕7 t ` M ],这就会有998次无效的系统调用,造成严重的性能浪费。有问题就要解决 i I / C # i t,那y k x 4NIO问题的本质就是频繁轮询导致的无效系统调用。
5. IOc ! V 6 r ) f & Q ? s5.1. IO多路复用之select/poll
Select是内核提供的系统调用,它支持一次查询多个系统调用的可用状态,当任意一个结果状态可用时就会返回,用户进程再发起一次系统调用进行数G ^ r U l s据读取。换句话说,就是NIO中N次的系统调用,借助Select,只需要发l . q T起一次系统调用就够了。其IO流程如下所示:
但是,select有一个限制,就是存在连接数限制,针对于此,又提出了poll。其与select相比,主要是解决了连接限制。
select/epoll 虽然解决了NIO重复无效系统调用用的问题,但同时又引入了新的问题。问题是:
- 用户空间和内核空间之间,大量C 0 ( | Z ^ J A的数据拷贝
- 内核& 4 @循环遍历IO状态,浪费CPU时间
换句话说,select/poll虽然减少了用户进程的发起的系统调用,但内核的工作量只增d S I ^ + ? h ;不减。在高并发的情况下,内核的性能问题依旧。所以select/poll的问题本质是:内核存在无效的循环遍历。
5.2. IO多路复用之epoll
针对selecl h Ut/pool引入的问题,我们把解决问题的思路_ C v ] } @ a v Q转回到内核上i V f s 0 o $,如何减少内核k W [ S Q l e [ j重复无效的循环遍历呢?5 + / j变主动为被动,基于事件驱动来实现。其流程图, T e M如下所示:
epoll相较于select/poll,多了两次系统调用,其中epoll_create建立与内核的连接,epoll_ctl注册事件,epoll_wait阻塞用户进程,等待IO事件。
epoll,已经大大优化了IO的执行效率,但在IO执行的第一阶段:数据准备阶段都还是被阻塞的。所以这是G D v 2 w R &一个可以继续优化的点。
6. IO 模型之信号驱动IO(G a X 5 9 M HSIGIO). U,内核立马发给用3 K G D l 3 *户进程一个信号,说”数据准备好了,快来查收“,用户进程收到信号之= 8 k V Y后,立马调用recvfrom,去查收数据。
乍一看,信号驱动式I/O模型有种异步操作的感觉,但是在IO执行的T [ Z G n s第二阶段,也就是将数据从内核空间复制到用户空间这个阶段,用户进程还是被阻塞的。
综上,你会发现,不管是BIO还是NIO还是SIGIO,它们最终都会被阻塞在IO执行的第二阶段。那如果能将IO执行的第二阶段变成非阻塞,那就完美了。
7. IO 模型i ? , 7 8 l L | (之异步IO(AIO)
异步IO真正实现了IO全流程的非阻塞。用户进程发出系统调用后立即返回,内核等待数据准g / O 0 h U Q E备完成,然后将数据拷贝到用户进程缓冲区,然后发送信号告诉用户进程IO操作执行完毕(与SIC @ & 8GIO相比,一个是发送信号告诉用户进程数据准备完毕,一个是IO执行完毕)。其流程如下:
所以,之所以称为异步IO,取决于IO执行的第二阶段是否阻塞。_ c 6 O因此前面讲的BIO,NIO和SIGIO均为同步IO。
8. 总结
梳理完这些IO模型后,之前一直处于懵懂状态的阻塞,非阻塞,同步异步IO,终于算是有个概念了。同时也纠正了自己一直以来的误解,所以一路走来,愈发觉得返璞归真的重要性,只有如此,才能在快速更迭的技术演进中,以不变应万变。
本文综合多方资料写就,难免纰漏,但只有写下来,才能得以指正。v / r d / g M所以,烦请各位看官不吝赐教。