NIO
虽然说是java的Nio,但是实际上我这篇文章要讲的主要是操作系统层面上的NIO
之所以是这个标题还有是在java的笔记中是因为我是在学Java的的时候接触到这个概念的。
下面进入正题
了解NIO(非阻塞IO)之前,我们先了解一下传统的BIO(阻塞IO)
BIO(或者叫OIO)
传统的,最简单的进行io操作的方式,也就是当应用程序调用read或write等方法时,调用方法的线程进入阻塞状态,一直到io操作完成
这种方式有个明显的缺点就是,在网络io中,一个线程只能建立一个连接,因为需要通过read阻塞的方式时刻准备接收用户的请求。
那么一旦我们的系统需要同时处理大量请求,就需要同时维持同样数量的线程。
优点: 线程阻塞时, 消耗的CPU资源小
缺点: 高并发时容易耗尽线程资源, 且大量线程会引起线程切换的巨大开销, 造成响应不及时
NIO
引用一段话
- 应用B响应速度很快,在应用A读取TCP缓冲区时,就已经把响应数据返回了。那么应用A就可以顺利获取到数据,皆大欢喜。
- 应用B响应比较慢,在应用A读取TCP缓冲区时,还没有将响应数据返回了。这个时候操作系统就面临两种选择:
选择一:把应用A的本次调用阻塞在这,等到应用B的数据达到了缓冲区再重新唤醒应用A。
选择二:告诉应用A你要的数据还没来,你等会再来问。
如果说BIO是第一种选择,那么NIO就是第二种选择,当响应端来不及发送响应时,先发送一个数据未准备的信息回去,这样请求端收到后就可以时不时进行轮询,不断主动询问数据准备好没有。
在轮询的间隙线程不是阻塞的,可以做别的事情,所以叫非阻塞IO
NIO, 靠着Linux提供的可以立即返回的read(), 在第一步中是不阻塞的, 在第二步中仍然是阻塞的.
优点: 把应用线程的read调用, 分割出了第一步和第二步, 应用线程可以做更多实时的控制
缺点: 第一步中的轮询, 大量占用CPU时间, 降低系统资源利用率, 所以一般web服务器也不使用这样的NIO
两种方式都不尽如人意,那有没有同时结合两者优点的方式:既不用大量轮询,也不用同时开启大量线程还能同时处理多个请求的方式呢?
我们直接让一个线程接收所有的请求,然后在没有请求的时候就阻塞等待不就行了吗☝️🤓💡
但是如何让一个线程接收通过socket的请求呢🤔(这里以网络举例,以文件举例的话是如何让一个线程读写多个文件)
这就需要操作系统的支持
下面介绍操作系统的IO多路复用
IO多路复用
IO多路复用是一种同步IO模型, 是操作系统提供的能力, 使用少量线程监听Linux的多个IO文件描述符, 也就是线程复用于多个IO请求.
如果有任意IO文件描述符就绪, 就会告知应用进行读取,
如果没有IO文件描述符就绪, 就会阻塞应用线程.
Linux中, IO多路复用的实现是select/poll/epoll
IO多路复用的作用 : 线程复用于多个请求, 用较小的线程开销来支持更多的并发请求
NIO结合IO多路复用后, select/poll/epoll替代了轮询.
下面细讲一下这三种方式的不同
select/poll/epoll
Linux上的IO多路复用演变经历了3个阶段:
- select
轮询遍历IO文件描述符, O(n)的线性时间复杂度, 因此随着IO文件描述符的增多, 性能下降
单进程能够打开的文件描述符有数量限制, 导致应用的最大连接数是1024
调用select时, 还需要文件描述符在用户空间和内核空间的拷贝, 影响性能 - poll
同样是轮询遍历IO文件描述符, O(n)的线性时间复杂度, 因此随着IO文件描述符的增多, 性能下降
无文件描述符的数量限制
调用poll时, 还需要文件描述符在用户空间和内核空间的拷贝, 影响性能 - epoll
解决了select和poll的全部缺点,
采用注册回调机制, 替代了轮询遍历,
无文件描述符的数量限制
利用mmap减少了文件描述符在用户空间和内核空间的拷贝
因此性能高
以上就是现在使用的NIO的机制讲解(用法这里不讲,因为我还没有实践过,后面实践了补上)
AIO
都讲到这里了,感觉NIO的机制还是不够现代,我们接下来继续讲解一下现在用得最多的AIO的机制。
💬 评论区
暂无评论,来发表第一条吧。