谈谈面试中常问的I/O模型

一、IO介绍

1.1 Java中IO的分类

以下部分源自网络相关资料。

  1. IO按照处理的数据类型可分为:
    (1)面向字节操作的I/O接口:inputStream,outputStream
    (2)面向字符操作的接口:Reader,Writer
  2. IO按照数据的传输方式可分为:
    (1)面向磁盘操作的I/O接口:File
    (2)面向网络操作的I/O接口:Socket

1.2 Unix中的五种IO模型

以下分类的前提都是Linux/Unix环境下的网络IO,这点需要注意一下。
一个输入操作通常包括两个阶段:

  1. 等待数据准备好
  2. 从内核向进程复制数据

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

Unix中有五种I/O模型:

  1. 阻塞式I/O
  2. 非阻塞式I/O
  3. I/O复用
  4. 信号驱动I/O
  5. 异步I/O

阻塞I/O模型:
最常见的一种IO模型,之前介绍过,一个read操作是分两个阶段的,第一个阶段是,等待数据准备就绪,第二个阶段是将数据拷贝到调用这个IO的线程中。阻塞是发生在第一个阶段的,当数据没有准备好时,会一直阻塞用户线程,当数据就绪后再将数据拷贝到线程中,并返回结果给用户线程。

其实,大部分的socket接口都是典型的阻塞型。所谓阻塞型的接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。

通过介绍了阻塞IO,我们很容易就会发现它的问题,那就是阻塞会是用户线程无法进行任何运算和请求。一般我们的处理这种问题的情况是使用多线程,每个链接创建一个线程,或是使用线程池来管理线程,或许可以缓解部分压力,但是不能解决所有问题。多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

非阻塞式I/O
非阻塞IO模型是这样一个过程,当应用程序发起一个read操作时,并不会阻塞,而是立刻会收到一个结果。应用程序的线程发现返回结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户内存,然后返回。

这样的一个过程,其实是需要用户线程不断的去询问系统是否准备好了数据,这样就会一直占用CPU资源。但是这种模型是在只专门提供某种功能的系统才有。

多路I/O复用技术
在介绍多路复用I/O时就要先简单说明一下,select函数和poll函数。

select函数
select函数允许进程指示内核等待多个事件中的任何一个事件发生,并且只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。

举个例子,我们可以调用select,告知内核仅在下列情况发生时才返回:
集合 {1,4,5} 中的任何描述符准备好读;
集合 {2,7} 中的任何描述符准备好写;
集合 {1,4} 中的任何描述符有异常条件待处理;
已经经历10.2秒;
也就是说,我们调用select告知内核对哪些描述符(读、写或异常条件)感兴趣以及等待多长时间。

poll函数
poll函数起源于SVR3,最初局限于流设备。SVR4取消了这种限制,允许poll工作在任何描述符上。poll函数提供的功能与select函数类似,但是poll没有最大文件描述符数量的限制。

select函数和poll函数将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select函数或者poll函数时会再次报告这些文件描述符, 所以他们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

简单的解了select函数和poll函数后,下面我们就继续说多路I/O复用模型。多路IO复用模型就是调用select或poll函数,并且此模型的阻塞过程就是发生在调用这两个函数中的,而不是发生在真正的的I/O系统调用上的,使用select或poll的好处在于可以用单个线程或进程,处理多个网络连接的IO。整个过程就是select或poll函数会不断的轮询所负责的socket,当某个socket有数据到达了,就通知用户线程或进程。

猜你喜欢

转载自juejin.im/post/5ef581f26fb9a07eaf26a39a
今日推荐