BIO的问题
上一节我们提到BIO在读取数据时,需要阻塞当前线程,因此会导致当前线程无法继续其他工作;
由于数据传输是个漫长的过程,那么是否可以在此过程中,不阻塞当前线程呢?
基于以上想法,Unix提供了非阻塞的IO模型,也就是我们常说的NIO模型(No-Blocking)。
BIO的演进-NIO
相比于BIO,NIO(Non-Blocking I/O)在等待数据传输阶段不会被阻塞,也就是说应用线程不会被内核挂起, 同时应用
会不断轮询(polling)内核数据是否已经准备好。如果某次轮询发现数据已经准备好了,再直接发系统调用阻塞取数据.
备注:
轮询:应用进程对非阻塞描述符循环发送系统调用,以查看某个操作是否就绪
NIO的时序图
如上图所示:
- 应用在调用recvfrom后(类似于socket的read),系统内核会如果没有准备好数据,则直接返回;
- 应用端可以继续轮询调用recvfrom函数,当最后一次调recvfrom后,数据准备完成,进入到阻塞状态,直到数据由内核buff复制到应用buff;
不足
相比于BIO,虽然NIO在数据准备(传输)阶段实现了异步,应用线程不会被挂起,避免了频繁线程切换带来的系统开销;但是在处理大量客户端的IO操作(fd操作)的时,轮询也会对系统内核以及CPU造成巨大的压力。试想一下,几万个客户端连接到服务器请求IO时,轮询的方案就会显得捉襟见肘了。
那么要如何解决大量链接访问服务器问题呢?我们下节IO的多路复用中再进行介绍;
面试题:
1.我们通常NIO是一种"同步非阻塞"模型,而AIO是一种“异步非阻塞模型”,请解释什么是“阻塞”什么是“异步”?
为什么NIO在同步的情况下,还说它是一种非阻塞模型?
答:NIO的同步与非阻塞,分别指的是网络I/O中的两个阶段:
第一个阶段:数据的网络传输阶段;
第二个阶段:数据的缓冲区拷贝阶段;
在第一个阶段中,数据准备好前,根据线程是否会被“挂起”,区分出”阻塞“与”非阻塞“两种方式;
阻塞方式:当数据没准备好时,本线程”暂停“;
非阻塞方式:当数据没准备好时,线程不会被挂起;
NIO采用轮询的方式获取数据传输状态,所以线程不会被挂起,属于“非阻塞模式”。
而“同步”与“异步”则是指第二阶段数据拷贝的过程中,线程是否需要等待;
同步方式:数据拷贝过程,线程需要“等待”,无法进行其他工作;
异步方式:数据拷贝由内核完成,在完成后以事件的方式,通知线程;
因此NIO在由内核buff向应用buff拷贝过程中,线程需要“等待”,因此是一种同步方式;
而AIO中内核会负责由“内核buff”向“应用buff”的拷贝,完成后在通知应用,因此是一种异步方式;
2.为什么Netty使用NIO模型,还不使用AIO模型呢?
答:显然AIO模型比NIO必须更加“高效”,但是由于目前底层Unix对于AIO的支持并不是非常完善,这就导致AIO的效率比NIO没有明显的改善,反而会增加netty的开发/维护复杂读,因此netty也放弃(删掉)了对于AIO的支持。
这里可以参考作者的原话:
Not faster than NIO (epoll) on unix systems (which is true)
There is no daragram suppport
Unnecessary threading model (too much abstraction without usage)