前言
我们的IO操作:文件的操作,网络请求的操作都跟IO有关,我们需要了解下目前的IO模型有哪些。在这之前我们需要了解缓存IO
缓存IO
缓存IO又叫做标准IO,所有的IO写入都会先写向操作系统内核的缓存,再由操作系统刷新到硬盘。返回来也是一样,读取文件也是先读取到操作系统的缓存,然后再给程序。
缓存I/O的缺点是数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的CPU以及内存开销是非常大的。
同步、异步、阻塞、非阻塞的概念
同步和异步最大的区别就是被调用方的执行方式和返回时机。 同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回,然后再做事情,做完之后再想办法通知调用方。
阻塞和非阻塞最大的区别就是在被调用方返回结果之前的这段时间内,调用方是否一直等待。 阻塞指的是调用方一直等待别的事情什么都不做。非阻塞指的是调用方先去忙别的事情。
阻塞、非阻塞说的是调用者。同步、异步说的是被调用者。
常见的IO模型
一个Socket请求服务器,会经过哪些步骤?
1、一个Socket会被分成多个包,经过多个路由到达服务器,进入操作系统的缓存区,也就是说要等待所有的Socket包都到达服务器
2、操作系统将数据拷贝给应用程序缓存
文件的IO也跟网络的IO类似,第一步需要等待操作系统从硬盘中读取数据到缓冲区
同步阻塞IO
同步阻塞说明上面两步操作都会被阻塞,既要等待数据的准备,又要等待数据的拷贝
同步非阻塞IO
同步阻塞相当在第一步的时候你可以去做别的事情,只是做的过程回来询问数据是否准备好了。当应用程序调用recvform后并不会阻塞,也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好, 此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。
IO多路复用
这个是重点中的重点,现在的IO模型大多都是这个,Nginx,Redis,Java的NIO都是这个实现的
IO多路复用是指操作系统会有一个线程监听所有的IO请求,如果有一个请求的数据已经准备好了,那么就将该请求发送给预定的程序
目前的Unix系统的IO多路复用的函数有select、poll、epoll,select是最早出现的多路复用api,但是有一些缺点,比如最大只能有1024个线程,线程不安全;接着poll就修复了select的一些问题,解决了1024的问题,但还是线程不安全;最后epoll出现了,他解决了select的所有问题,现在的Linux都是使用它完成多路复用模型,但epoll只能在Linux上实现。还有一种叫kqueue的IO多路复用函数,但它是macos系统的东西。
想比较于同步非阻塞IO,它的改进之处在于原来用户进程轮询这事交给操作系统内核线程实现 ,如果数据准备好,操作系统就会通知用户进程,将数据拷贝,而且这个内核进程可以等待多个Socket,能实现对多个IO端口进行监听
如果连接数不高的话,使用IO多路复用并不一定比使用多线程+阻塞IO的Web Server性能好,因为IO多路复用的优势并不是对于单个连接处理得快,而是在于能处理更多的连接 。
IO多路复用归为同步阻塞 ,为什么?因为调用select、poll、epoll这些复用函数其实也是阻塞的,但是这种阻塞丢给操作系统后台去执行,用户进程不需要阻塞而已。
异步非阻塞IO
用户进程如果想进行异步非阻塞IO,调用aio_read系统后,等到Socket数据准备好,内核直接复制数据给进程(不是进程去内核拷贝数据了),然后内核向进程发送通知,IO这两个阶段都是非阻塞的
Linux下的AIO库函数实现异步,但用得很少,目前很多开源异步IO库有:libevent、libev、libuv。
Java中的四种IO模型
- 1、Java传统IO模型,即阻塞IO
- 2、NIO是同步非阻塞
- 3、同步NIO实现的Reactor模式,即IO多路复用模型的实现
- 4、同步AIO实现的Proactor模式,即是异步IO模型的实现
- 本篇博客是读完该公众号后写的Java技术栈