我所理解的IO基础:IO、NIO、AIO的一些基本概念

1,名词解释

IO:Input/Output表示数据的交换,广义上的IO包括文件IO、网络IO、其他IO。本篇只讨论网络IO,也就是socket IO。

IO:默认表示传统的Blocking IO(阻塞式IO)。
NIO:JDK1.4推出,Sun官方叫做NewIO,民间叫做Non-blocking IO(非阻塞IO)。
AIO:JDK1.7推出,Asynchronous IO(异步IO),也叫NIO2.0。

2,为什么分为这几种IO?

JVM虚拟机运行在操作系统上,JVM的native方法相当于是对操作系统API的封装(抽象)。所以,首先得理解操作系统支持的IO。
Linux操作系统支持的网络IO有5种形式:

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路复用( IO multiplexing)
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)

由于signal driven IO在实际中并不常用,只介绍其他4种IO Model。介绍之前,得需要了解一些概念。

2.1,网络数据是如何从网卡内存转移到用户线程中的?

用户线程发送读取命令到操作系统,操作系统将数据从网卡中读取到内核空间中,再从内核空间转移到用户线程中。

2.2,什么是内核空间,什么是用户线程空间?

为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对32位的linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

一句话总结:为了安全操作,网卡中的数据需要先读到内核空间中,再转移到用户空间中。

最重要的是要理解:分为两步操作来完成一个读的操作

2.3,什么是线程阻塞?

用户线程发起请求,告诉操作系统,需要读取数据。操作系统去读取数据放到内核空间中,再从内核空间写到用户线程中,这都是需要时间的。用户线程只能等待数据就绪之后才能执行,那这段时间内总不能占着CPU使用权不放吧?于是,用户线程阻塞等待获取到数据(无法继续执行后续代码),会主动释放掉CPU执行权。

2.4.1,什么是阻塞 I/O(blocking IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

图片来源于网络

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:将数据从网卡读到内核中。由于网络的接收是需要时间的(网络用户的数据并没有完全传输完),从网卡读到内核也是需要时间的,所以第一阶段阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

blocking IO的特点就是在IO执行的两个阶段时,用户线程都处于阻塞状态。

2.4.2,什么是非阻塞 I/O(non-blocking IO)

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

图片来源于网络

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。

2.4.3,什么是I/O 多路复用( IO multiplexing) 在non-blocking IO中,用户线程不断的主动询问一个数据有没有准备好,比较浪费cpu。于是,由一个线程来主动轮询多个数据是否准备好,每次都可以返回多个标识符,表示哪些数据内核空间已经准备好了,用户可以读取了。

图片来源于网络

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

用select的优势在于它可以同时处理多个connection。

2.4.4,什么是异步 I/O(asynchronous IO)

inux下的asynchronous IO其实用得很少。先看一下它的流程:

图片来源于网络

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

2.5,几种IO的比较

各个IO Model的比较如图所示: 图片来源于网络

2.6,同步IO和异步IO的区别

在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
  • An asynchronous I/O operation does not cause the requesting process to be blocked;

这里的“I/O operation”包括上文提到的两步:1,内核空间数据的准备;2,将数据从内核空间拷贝到用户空间。

同步IO和异步IO两者的区别就在于同步IO做”IO operation”的时候会将线程阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。只有最后一种asynchronous I/O是异步IO。

=======================================分割线==================================

举个例子说明。

假设你有个朋友A,托你帮他在花店定做一束花,再帮他送给B。

blocking IO:你在花店里一直等待花做好,然后亲自送给B,等待B签收。这期间你不能干别的事情。

non-blocking IO:你不停给花店打电话询问花是否做好了,确认做好了之后你去拿花并且亲自送给B,等待B签收。

每次打电话询问A的一束花,效率太低。于是你又开始帮朋友A1,A2...An在花店定做花并且送给B1,B2...Bn。

这样每打一次电话,都有可能收到许多花准备好的通知。(不一定有先后顺序,毕竟定做的花也有难易程度)

IO multiplexing:每次打电话给花店可以询问许多花是否准备好,花店给你的回复是其中某些花准备好了。于是你把这些已经准备好的花拿到并且一个一个的亲自送给目标B,等待B签收后送下一个目标B。

每次都是你亲自送花,这样效率太低了。某天花店推出了一项业务,可以帮助顾客送花,这样就不用你亲自送了。

asynchronous IO:你告诉花店定做的花,并且告诉花店这束花送到目标B的地址,让花店去送给B。然后你随意干什么都行。等花店把花送达B的时候会给你发个通知。

朋友A就是客户端,目标B就是用户线程,你就是Java的IO底层程序。你雇佣的人就是线程池。

参考资料:
1,Linux IO模式及 select、poll、epoll详解
2,IO模型解惑

猜你喜欢

转载自my.oschina.net/u/3466682/blog/1603535
今日推荐