文章目录
- 前言
- 什么是IO模型?
- 阻塞IO
- 非阻塞IO
- 多路复用IO
- 信号驱动IO
- 异步IO
- 结语
前言
最近学netty,当然无法避免IO模型这部分知识。
我尽量用最简洁的语言来讲清楚这个东西。
什么是IO模型?
既然最近学netty,就拿它来举例子。
比如两个应用通过网络通信(基于TCP)。
应用1发送消息应用2。TCP是有一个发送缓冲区和接受缓冲区的。
大概是这么一个状态
通信的过程就是,应用1发出的数据先发到TCP发送缓冲区,再经由网络传输给应用2所在主机,此时由对应主机的TCP接收缓冲区接收,接收到后,应用2再去接受缓冲区读。
那么应用2从TCP接收缓冲区中读数据的这个操作,就是一个IO请求(把数据从网卡中最终读到内核空间的TCP缓冲区,再从内核读到读到用户空间,是一个网络IO中的读操作),请求把数据读到用户空间。
那么这就要引出IO模型了。即上文中我们读数据这个过程的具体流程。
我们要读数据,面临着两种情况,网络上有数据到达和没有数据到达。我们调用read函数去读数据,此时内核缓冲区中可能有数据,也可能没数据。那么有数据我们当然可以直接读过来。没有数据的时候呢呢?我们是等待还是直接返回?这里就是区别几种IO模型的地方了。下面详细说一下这5个IO模型。
阻塞IO
阻塞IO
以读操作为例。就是在内核中缓冲区没有数据可读时阻塞等待,直到有数据可读,再读取数据并返回。
也就是说即使当前数据没准备完成,依旧阻塞等待在内核态,直到数据准备完成则开始读到用户空间。
对于写操作,若写缓冲区已满,则此时无法写,则阻塞等待,直到写缓冲区有空间了才去写。
很容易发现,这个时间可以省掉,本来可以等数据准备完成或者缓冲区可写时再去进行IO操作的。
阻塞操作的性能很差。
它的优点在于实现简单。响应也快,因为数据准备完成就会立即进行数据的复制。
非阻塞IO
非阻塞IO
直接对比阻塞IO的概念。
既然在阻塞IO时在准备时直接阻塞等待。那么非阻塞自然是不阻塞等待。
比如读操作,没有数据可读就直接返回,不再阻塞等待。一般循环去读,直到数据准备完成,就把数据从内核拷贝到用户空间。
写操作也类似,在写缓冲区满时就返回,循环去调用,直到某次调用发现写缓冲区有空间,则进行写操作。
循环调用虽然没有阻塞,在调用返回时也可以做一些其他事情。
即每次做点其他的事再去调用IO操作来看是否可以进行操作。
这也导致一个问题,由于不是连续调用IO操作函数,中间穿插了其他操作,可能数据准备好时没有立即去进行IO操作,导致一些请求延迟。
多路复用IO
linux多路复用有三种实现select,poll,epoll。这里仅说select,主要讲思想。
多路复用好处在于一个进程监控多个进程的IO操作。什么意思?
假如我们有进程A,B,C,这三个进程都想进行读操作,那么我可以把这些进程IO请求注册到多路复用器上。然后使用一个进程调用select函数,该进程通过select函数来监听A,B,C的IO准备情况。当A,B,C的IO在内核都没有数据可读时,select会阻塞,直到有某个进程IO对应的缓冲区有数据可读,触发读事件,那么select就从阻塞状态恢复,自己进行读操作,或者通知原进程进行读操作。
梳理一下。就是把每个进程想监听的IO事件注册到多路复用器,然后另外一个进程来调用select监控这些IO,触发事件则可以通知原线程进行读操作。
即把原本阻塞IO的阻塞时间和非阻塞IO的空转时间省掉了。直接把事件交给一个进程去监听,有事件则说明当前数据准备完成或者缓冲区准备完成,可以进行对应IO操作了。
多个进程的情况下,不会像阻塞IO一样都阻塞,或者非阻塞IO那样,一直空转。
信号驱动IO
信号驱动IO
想进行读操作,注册一个信号处理函数给内核就返回,不阻塞。
在数据准备完成,内核就会发送信号,进程收到就会触发设定好的回调函数来进行读操作。
涉及信号相关操作,实现有难度。
同时信号队列大小有限,可能无法处理太多请求。
异步IO
上述几个模型,最多只在等待数据的部分异步由其他人来做。
而复制数据一般都需要本进程进入内核态完成数据拷贝。
而异步IO则是把这个数据复制的部分也异步来做。
即在系统调用读操作时,直接返回,但是并不会返回任何结果,因为此时还没执行完成。等待数据还有复制数据的部分都交给内核来进行。在内核完成数据复制后通知进程IO操作完成
缺点在于实现难度大,应用难度大。
结语
这篇文章介绍了这几个IO模型。
仅为个人理解,这块也看过不少文章博客。
了解这些,再看Netty的IO模型可能就更好理解。
后面可能会更一期Netty的IO模型。
感谢阅读,欢迎批评指正。