目前对于市面上绝大多数的应用来说,不能实现的业务功能太少了。更多的是对底层细节,性能优化的追求。其中IO就是性能优化中很重要的一环。Redis快,mysql缓冲区存在的意义。都跟IO有着密切关系。IO其实我们都在用,输入输出流这块。但是没关注到计算机组成原理那块我觉得还是差点意思。把整个IO理解清楚,就得从计算机的交互开始。我近期学习了很多篇IO文章,特地做下总结。
首先大概念,IO,输入输出。
输入理解为键盘,输入给谁,肯定是电脑。输入到电脑里做什么,一般就是用做存储。那就可以理解为从外部媒介到电脑内核这个过程就是输入。同理经过电脑内核展现出来的就叫输出。那么电脑内核在做什么事?电脑内核又分为用户空间和内核空间。内核空间是操作系统层面的,用户无权直接访问。用户空间是个人的。它与内核空间做任何信息交互就是我们编程领域说的IO了。一次操作系统的IO分数据准备和数据复制。讲到IO就离不开IO模型。IO模型理解就是同样操作,不同模型产生不同效率的一种方式。常见的三种 BIO,NIO,AIO。NIO中又多分为select,poll.epoll模式(这三类模式多路复用),说白了就是操作系统提供的三类监听socket的函数。NIO中又提出了事件驱动和信号驱动的概念。其中epoll模式的信号驱动就是目前主流的IO模型。很多源码中用的都是这种模型。
就很直观举个例子,理解下各类IO模型。但例子仅仅是针对IO中的数据准备阶段
小明去吃饭,餐厅总共有五个位置。到那里发现没位置了,就一直等。等到有位置就可以吃。这是BIO。
小明去吃饭,餐厅总共有五个位置。到那里发现没位置了,餐厅告诉他晚点再来,于是他就走了第二次再来询问有没有位置,直到他问到刚好有位置了就可以吃。这是NIO。
小明去吃饭,餐厅总共有五个位置。到那里发现没位置了,餐厅告诉他等有位置了再通知他来,于是他只是约了号,餐厅有位置了就告诉他来吃,但此时他是不知道是哪个位置。只能自己去再问一次哪个位置空缺了。这是NIO多路复用。采用事件驱动。
为了解决不知道是哪个位置的无效遍历,加上了信号驱动。epoll中用的就是这个模式。
把上面的所有问题统一为一类,都是为了解决数据准备阶段的监听回复。但没解决数据复制阶段的阻塞。那块还是同步的。AIO就是把NIO的epoll模式后面数据复制的过程也做成异步。就完成了真正意义上的异步。
再分析下每种模型的指令,就是因为发送的指令不同才呈现出不同的效果。
BIO 直接发送recvfrom指令,并且内核无明确返回。
NIO 直接发送recvfrom指令,并且内核明确返回EWOULDBLOCK错误码表示未准备好数据。
NIO多路复用-select模式。发送select指令,等待内核返回任意一个。都是同一个进程发起的select指令会监听内核中的多个fd。fd就是每操作文件是内核的一个状态码。select模式监听的IO最大连接数有限,在Linux系统上一般为1024。因为采用的是固定长度的 BitsMap实现。
NIO多路复用-poll模式。发送poll指令,同select模式。等待内核返回任意一个。都是同一个进程发起的select指令会监听内核中的多个fd。poll模式采用动态数组实现,主要解决了的IO最大连接数有限问题。
NIO多路复用-epoll模式。主要三个指令epoll_create、epoll_ctl、epoll_wait。发送epoll_create指令,一旦基于某个fd
就绪时,内核会采用回调机制,迅速激活这个fd
,当进程调用epoll_wait()
时便得到通知。这时候是能精确定位到fd的。但还存在一次调用epoll_wait调用主动询问的过程。于是便出现了信号驱动IO,信号驱动不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号,调用sigaction
的时候建立一个SIGIO
的信号。内核数据准备好后,再通过SIGIO
信号通知应用进程。这样就不需要主动询问了。
AIO直接发送aio-read指令,就可以完成全部流程的操作。
I/O模型的应用非常广泛,它们被集成在多种主流框架中以提高性能和可扩展性。如Netty,Redis。理解这些I/O模型的原理和特点,可以帮助我们更好地设计和优化程序,提高系统的性能和可靠性。希望本文能够帮助读者深入理解I/O模型。
参考 看一遍就理解:IO模型详解 - 知乎