如何理解阻塞IO和非阻塞IO、同步IO、异步IO

如何理解分5种IO模型、阻塞IO和非阻塞IO、同步IO、信号驱动IO和异步IO

  • 前言
  • 一、IO的概念
  • 二、5种IO模型
    • 阻塞IO模型(blocking IO)
    • 非阻塞IO模型(nonblocking IO)
    • IO复用模型(IO multiplexing)
    • 信号驱动IO模型
    • 异步IO模型(Asynchronous I/O)

前言

初学IO模型时,经常搞不明白阻塞(blocking)和非阻塞(non-blocking),同步(synchronous IO)和异步(asynchronous IO)这些IO模型区别在哪。。这是由于网络上的很多关于IO模型的资料其技术背景不一样导致的。 本文讨论的背景是Linux环境下的network IO。

一、IO的概念

IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间<–>内核空间、内核空间<–>设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。
LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。
对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。
简单的来说,对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
(1)等待数据准备 (Waiting for the data to be ready)
(2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。
在这里插入图片描述

二、5种IO模型

5种IO模型分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型、异步IO模型;

阻塞IO模型(blocking IO)

如何理解阻塞IO和非阻塞IO、同步IO、异步IO_第1张图片
进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。

1、典型应用:阻塞socket、Java BIO;

2、特点:

进程阻塞挂起不消耗CPU资源,及时响应每个操作;

实现难度低、开发应用较容易;

适用并发量小的网络应用开发;

不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。

非阻塞IO模型(nonblocking IO)

如何理解阻塞IO和非阻塞IO、同步IO、异步IO_第2张图片
进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。

对于上面的阻塞IO模型来说,内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。

1、典型应用:socket是非阻塞的方式(设置为NONBLOCK)

2、特点:

进程轮询(重复)调用,消耗CPU的资源;

实现难度低、开发应用相对阻塞IO模式较难;

适用并发量较小、且不需要及时响应的网络应用开发;

IO复用模型(IO multiplexing)

如何理解阻塞IO和非阻塞IO、同步IO、异步IO_第3张图片
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句:所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。因此select()与非阻塞IO类似。

1、典型应用:select、poll、epoll三种方案,nginx都可以选择使用这三个方
案;Java NIO;

2、特点:
专一进程解决多个进程IO的阻塞问题,性能好;Reactor模式;

实现、开发应用难度较大;

适用高并发服务应用开发:一个进程(线程)响应多个请求;

信号驱动IO模型

如何理解阻塞IO和非阻塞IO、同步IO、异步IO_第4张图片
在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函
数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到
信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。
1、特点:回调机制,实现、开发应用难度大;

异步IO模型(Asynchronous I/O)

如何理解阻塞IO和非阻塞IO、同步IO、异步IO_第5张图片
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。也就说用户线程完全不需要实际的整个 IO 操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。
也就是说在异步 IO 模型中,IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。
1、典型应用:JAVA7 AIO、高性能服务器应用
2、特点:
不阻塞,数据一步到位;Proactor模式;
需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;
实现、开发应用难度大;
非常适合高性能高并发应用;
到目前为止,已经将五个IO模型都介绍完了。现在回过头来回答最初的那几个问题:阻塞(blocking)和非阻塞(non-blocking)的区别在哪,同步(synchronous IO)和异步(asynchronous IO)的区别在哪。
先回答最简单的这个:blocking与non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还在准备数据的情况下会立刻返回。
在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。《UNIX网络编程》中给出的定义(其实是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;
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个系统调用。non-blocking IO在执行recvfrom这个系统调用的时候,如果kernel的数据没有准备好,这时候不会block进程。但是当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内进程是被block的。而asynchronous IO则不一样,当进程发起IO操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

你可能感兴趣的:(如何理解阻塞IO和非阻塞IO、同步IO、异步IO)