资讯专栏INFORMATION COLUMN

一篇文章带你彻底搞懂NIO

ziwenxie / 3451人阅读

摘要:阻塞当进行读写时,线程是阻塞的状态。当任何一个收到数据后,中断程序将唤起进程。接收数据当收到数据后,中断程序会给的就绪列表添加引用。当接收到数据,中断程序一方面修改,另一方面唤醒等待队列中的进程,进程再次进入运行状态如下图。

本篇文章目的在于基本概念和原理的解释,不会贴过多的使用代码。

什么是NIO
Java NIO (New IO)是 Java 的另一个 IO API (来自 java1.4) ,意味着可以替代标准的 Java IO API和 Java Networking API。 提供了一种与标准 IO API 不同的 IO 工作方式。

注意:Java的NIO只是说IO API,阻塞非阻塞才是IO的模型。

也有人称NIO为No-Blocking IO,非阻塞IO,但是这么说并不严谨。因为对于基础的IO操作API(比如文件IO,FileChannel),还是阻塞的模型。只有对Networking IO API才可以使用非阻塞的模型(configureBlocking(false))。

Java NIO中的Networking IO API,支持非阻塞IO模型,还实现了IO多路复用(IO Multiplexing)。对于服务端来说,可以用更少的线程支持更多的并发,大幅度提升了性能。

NIO中的阻塞与非阻塞

阻塞与非阻塞是从线程的角度出发的,这里指的是线程状态。

阻塞

当进行IO读写时,线程是阻塞的状态。此时会让出cpu控制权,不会占用cpu资源。

什么?不占用CPU资源?那是不是代表阻塞模型更好呢?

答案是并不是,虽然阻塞状态不会占用CPU,但是会发生线程的切换,线程切换时会有上下文保存转换的过程,需要CPU调度,是一个很昂贵的操作。

Java NIO中的基础IO API(非Networking IO API)还是阻塞的方式,只是使用方式从面向流(stream)编程面向块(buffer)了,和BIO本质上并没有什么区别。

非阻塞

非阻塞是指在进行IO操作的时候,如果设备还未准备好(比如socket还没有收到数据),操作会直接返回结果,不会让当前线程进入阻塞状态。

这样的优点是,使用者可以自行决定在数据未准备好时的操作。线程可以在没有数据期间去执行其他操作。
Networking API可以配置为非阻塞模型Channel.configureBlocking(false),配合Selector来实现多路复用功能。简单的说就是一个Selector监听多个socket io(对于unix系统来说,socket也是一个fd,也属于io),可以在一个线程中支持多个连接。当然在实际服务器开发时,就算是NIO模型,有些程序也不会只使用一个线程;但相比传统的Blocking IO方式来说,需要的线程数量也会大大减少了。(redis中就是使用了IO多路复用技术,并且只有一个线程监听socket io)

AIO

AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,新增了提异步非阻塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会执行回调通知相应的线程进行后续的操作。

多路复用

在I/O编程过程中,当需要同时处理多个客户端请求时,可以利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术通过把多个I/O的阻塞复用到同一个Select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型相比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些线程和进程的运行,降低了系统的维护工作量,节省了系统的资源,I/O多路复用的主要应用场景如下:

服务器需要同时处理多个处于监听状态或者多个连接状态的Socket

服务器需要同时处理多种网络协议的Socket

目前支持I/O多路复用的系统调用又select/pselect/poll/epoll。

select/epoll select

select的实现思路很直接。假如程序同时监视如下图的sock1、sock2和sock3三个socket,那么在调用select之后,操作系统把进程A分别加入这三个socket的等待队列中。

当任何一个socket收到数据后,中断程序将唤起进程。下图展示了sock2接收到了数据的处理流程。

所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面。如下图所示。

经由这些步骤,当进程A被唤醒后,它知道至少有一个socket接收了数据。程序只需遍历一遍socket列表,就可以得到就绪的socket。

这种简单方式行之有效,在几乎所有操作系统都有对应的实现。

但是简单的方法往往有缺点,主要是:

其一,每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。

其二,进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。

那么,有没有减少遍历的方法?有没有保存就绪socket的方法?这两个问题便是epoll技术要解决的。

补充说明: 本节只解释了select的一种情形。当程序调用select时,内核会先遍历一遍socket,如果有一个以上的socket接收缓冲区有数据,那么select直接返回,不会阻塞。这也是为什么select的返回值有可能大于1的原因之一。如果没有socket有数据,进程才会阻塞。

select低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。如下图所示,每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程。显而易见的,效率就能得到提升。

select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。

epoll

epoll是在select出现N多年后才被发明的,是select和poll的增强版本。epoll通过以下一些措施来改进效率。

原理:

创建epoll对象

如下图所示,当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。

创建一个代表该epoll的eventpoll对象是必须的,因为内核要维护“就绪列表”等数据,“就绪列表”可以作为eventpoll的成员。

维护监视列表

创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。

当socket收到数据后,中断程序会操作eventpoll对象,而不是直接操作进程。

接收数据

当socket收到数据后,中断程序会给eventpoll的“就绪列表”添加socket引用。如下图展示的是sock2和sock3收到数据后,中断程序让rdlist引用这两个socket。

eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。

当程序执行到epoll_wait时,如果rdlist已经引用了socket,那么epoll_wait直接返回,如果rdlist为空,阻塞进程。

阻塞和唤醒进程

假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。如下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。

当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。也因为rdlist的存在,进程A可以知道哪些socket发生了变化。

参考

Netty权威指南

https://zhuanlan.zhihu.com/p/...

http://tutorials.jenkov.com/j...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75498.html

相关文章

  • ES6指北【3】——5000字长文带你彻底搞懂ES6模块

    摘要:模块什么是模块什么是模块化玩过游戏的朋友应该知道,一把装配完整的步枪,一般是枪身消音器倍镜握把枪托。更重要的是,其它大部分语言都支持模块化。这一点与规范完全不同。模块输出的是值的缓存,不存在动态更新。 1.模块 1.1 什么是模块?什么是模块化? 玩过FPS游戏的朋友应该知道,一把装配完整的M4步枪,一般是枪身+消音器+倍镜+握把+枪托。 如果把M4步枪看成是一个页面的话,那么我们可以...

    ygyooo 评论0 收藏0
  • 以❤️简单易懂❤️的语言带你搞懂有监督学习算法【附Python代码详解】机器学习系列之KNN篇

    必须要看的前言 本文风格:以❤️简单易懂❤️的语言带你彻底搞懂KNN,了解什么是有监督学习算法。 认真看完这篇文章,彻底了解KNN、了解监督学习算法绝对是一样很简单的事情。 注:本篇文章非常详细,同时我也附加了Python代码,欢迎收藏后慢慢阅读。 目录 必须要看的前言监督学习算法KNN/K近邻算法1 算法原理1.1 实现过程1.2 距离的确定 2 算法的优缺点3 算法的变种3.1 变...

    MoAir 评论0 收藏0
  • 少啰嗦!分钟带你读懂Java的NIO和经典IO的区别

    摘要:的选择器允许单个线程监视多个输入通道。一旦执行的线程已经超过读取代码中的某个数据片段,该线程就不会在数据中向后移动通常不会。 1、引言 很多初涉网络编程的程序员,在研究Java NIO(即异步IO)和经典IO(也就是常说的阻塞式IO)的API时,很快就会发现一个问题:我什么时候应该使用经典IO,什么时候应该使用NIO? 在本文中,将尝试用简明扼要的文字,阐明Java NIO和经典IO之...

    Meils 评论0 收藏0
  • 牛啤~这个框架被大量使用,腾讯开源的RPC框架阿里的Dubbo全靠它

    摘要:分布式高并发微服务问阿里京东蚂蚁等大厂面试真题解析道跳槽涨薪必备精选面试题最新版大厂面试真题集点击这里免费领取点击这里免费领取 估计很多Java程序员平时主要的工作就是一些Web系统的业务开发,对于服务端IO程序以及网络通信编程做得并不多,但是对于高级或者资深程序员来说,IO通信以及服务端编...

    whidy 评论0 收藏0

发表评论

0条评论

ziwenxie

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<