IO分为同步IO和异步IO 两大类。
同步:是一种用户空间与内核空间的IO发起方式,同步IO是指用户线程主动发起的一方,内核空间是被动接收方
异步:异步IO是内核线程主动发起的一方,用户空间为被动接受方。
Java中的NIO 是NEW IO 而不是none-blocking IO,JAVA中用的是 多路复用IO
LINUX默认的线程数是1024个
1、用户线程发起访问(进入阻塞状态)
2、内核:
1)内核空间受到read系统的调用,内核就开始准备数据
2)内核空间将数据放入内核缓冲区
3)将内核缓冲区中的数据放入用户空间缓冲区
4)返回结果
3、调用返回
优点:开发简单
缺点:会为每一个访问创建线程,系统会创建多个线程,在高并发的场景下,需要大量的线程维护大量的网络连接,内存、线程切换的开销会非常大。
1、在用户线程发起调用的时候,会直接返回(不会阻塞),但是会一直自旋,不断轮循,消耗CPU
2、内核:等内核将数据准备好以后,准备就绪。
3、这时用户空间的轮循发现内核已就绪,进入阻塞状态,将内核缓冲区的数据复制到用户缓冲区
4、直至返回后,才会释放线程,解除阻塞。
优点:用户线程发起访问后,在内核系统准备数据的时候,就立马返回,不会阻塞。实时性较好
缺点:不断的自旋空转,会消耗CPU的性能。阻塞不会消耗CPU性能
Java 不会用这种IO。太消耗CPU的性能了。 Java中的NIO是 NEW IO,多路复用的IO 。而不是这个NIO(none-blocking io)
在Linux系统中,对应的 select/epoll 系统。
select–用户线程使用select查询就绪列表
epoll–内核就绪以后,将该socket放入就绪列表中。
epoll 是在Linux 2.6内核中提出来的。
epoll:当内核进入就绪状态(可读或者可写的时候),会有一个回调函数,通知用户线程,
某些或者某个socket连接中有IO就绪的状态。
多路IO复用 read操作流程
1、选择器注册(只有非阻塞通道才能进行选择器注册),将所有的socket连接注册进入选择器。
选择器是一个线程轮循,只轮循就绪列表
2、就绪状态轮循。只有内核空间就绪以后,内核就会将该socket加入到就绪的列表中
3、用户线程调用select查询方法,线程阻塞
4、用户线程根据socket连接,发起read系统调用,内核开始复制数据,将数据从内核缓冲区复制到用户缓冲区
5、复制完以后,内核返回结果,该线程才会解除阻塞状态。用户线程拿到数据,继续执行。
优点:与一个线程维护一个socket连接相比,一个选择器查询线程可以同时维护成千上万个socket连接,系统不必创建大量的线程,也不必维护这些线程。大大减小了系统的开销。
异步IO是由内核发起,用户空间为接收方。
在异步IO模型中,整个内核的数据处理过程中,将数据从网络物理设备(网卡),读取到内核缓冲区,将内核缓冲区的数据复制到用户缓冲区,用户程序都不需要阻塞。
read调用流程:
1、当用户线程发起read调用后,不用阻塞,就去干其他的事情了
2、内核空间读取数据到内核缓冲区,将内核缓冲区的数据复制到用户缓冲区
3、执行完第二部的操作后,想用户线程发起一个信号(signal),告诉用户线程read完成操作了
4、用户线程接收到信号,读取用户缓冲区中的数据,返回结果。
异步IO优点:在内核等待数据、将内核缓冲区的数据复制到用户缓冲区的过程中,用户线程都不是阻塞状态。
只需要注册一个回调函数。等到内核操作完成以后,通过回调函数给用户线程发一个信号(signal),
所以异步IO也叫,信号驱动IO
异步IO缺点:比较依赖内核系统,现阶段,Linux底层实现还是依赖epoll,目前并不晚上
大名鼎鼎的netty 是用的IO多路复用,而不是异步IO模型
IO多路复用:
epoll:内核将数据读到内核缓冲区的时候,就进入就绪状态,将socket连接写入就绪列表
AIO:
内核将数据读到内核缓冲区,再从内核缓冲区复制到用户缓冲区,再向用户线程发起一个信号(signal)