IO中的同步异步、阻塞非阻塞

作者 zhenlanghuo 日期 2017-05-21
IO中的同步异步、阻塞非阻塞

上一篇已经讲了同步异步和阻塞非阻塞各自的区别,这一篇来讲一下IO中的同步异步、阻塞非阻塞


UNIX中的5种IO模型

摘自:《UNIX网络编程 卷1》第6章

一个输入操作通常包括两个不同阶段:
(1)等待数据准备好;
(2)从内核向进程复制数据。

下面我们来看看不同的IO模型,这两个阶段是怎么进行的

阻塞式IO模型

默认情况下,所有套接字都是阻塞的

阻塞IO模型.png-54.9kB

进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者错误才返回。进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。recv成功返回后,应用进程开始处理数据报。

非阻塞式IO模型

进程把一个套接字设置成非阻塞是在通知内核:当所请求的IO操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。

非阻塞IO模型.png-95.1kB

前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用程序进程缓冲区, 于是recvfrom成功返回。我们接着处理数据。

当一个应用程序像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询(polling)。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间,不过这种模型偶尔也会出现,通常是在专门提供某一种功能的系统中才有。

IO复用模型

有了IO复用,我们就可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的IO系统调用上

IO复用模型.png-94.8kB

我们阻塞于select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用进程缓冲区。

I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

信号驱动式IO模型

我们也可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们

信号驱动IO模型.png-79.2kB

这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据报已准备好被处理,也可以是数据报已准备好被读取。

异步IO模型

告知内核启动某个操作,并让内核在整个操作(包含将数据从内核复制到我们自己的缓冲区)完成后通知我们。

异步IO模型.png-62.9kB

这种模型与信号驱动模型的主要区别在于:信号驱动式IO是由内核通知我们何时可以启动一个IO操作,而异步IO模型是由内核通知我们IO操作何时完成

各种IO模型的比较

前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞于recvfrom调用。相反,异步IO模型在这两个阶段都要处理,从而不同于其他4种模型。

同步IO和异步IO对比

POSIX把这两个术语定义如下:

  • 同步IO操作导致请求进程阻塞,知道IO操作完成;
  • 异步IO操作不导致进程阻塞。

5种IO模型比较.png-163.2kB

根据上述定义,我们的前4种模型————阻塞式IO模型、非阻塞式IO模型、IO复用模型和信号驱动式IO模型都是同步IO模型,因为其中真正的IO操作将阻塞进程。只有异步IO模型与POSIX定义的异步IO想匹配。

我的理解

结合前面一篇对同步异步、阻塞非阻塞的定义,谈谈我对上述IO模型的理解

前面4种模型都是同步IO模型,因为其中真正的IO操作将阻塞进程,这里真正的IO操作就是指第二阶段,从内核向进程复制数据,也就是在读写事件就绪后自己负责进行读写(自己完成事情);而第5种IO模型,对数据的读写,也就是从内核进程复制数据是系统做的(相当于叫人代办)

前面4中模型都是同步模型,其中,非阻塞式IO模型、IO复用模型和信号驱动IO模型都是同步非阻塞的,非阻塞体现在第一阶段,调用者不需要阻塞得去等待数据准备好,可以在数据准备期间去做一下其他的事情

BIO、NIO、AIO

  • BIO同步阻塞式IO,简单理解:一个连接一个线程
  • NIO同步非阻塞IO,简单理解:一个请求一个线程
  • AIO异步非阻塞IO,简单理解:一个有效请求一个线程

以下摘自《Netty权威指南》

很多人喜欢将JDK 1.4提供的NIO框架称为异步非阻塞IO,但是,如果严格按照UNIX网络编程模型和JDK的实现进行区分,实际上它只能被称为非阻塞IO,不能叫异步非阻塞IO。在早期的JDK 1.4和1.5 update10版本之前,JDK的Selector基于select/poll模型实现,它是基于IO复用技术的非阻塞IO,不是异步IO。在JDK 1.5 update10和Linux core2.6以上版本,Sun优化了Selector的实现,它在底层使用epoll替换了select/poll,上层的API没有变化,可以认为是JDK NIO的一次性能优化,但是它仍旧没有改变IO的模型

由JDK1.7提供的NIO 2.0新增了异步的套接字通道,它是真正的异步IO,在异步IO操作的时候可以传递变量,当操作完成之后会回调相关的方法,异步IO也被称为AIO。

NIO类库支持非阻塞读和写操作,相比于之前的同步阻塞读和写,它是异步的,因此很多人习惯于称NIO为异步非阻塞IO,包括很多介绍NIO编程的书籍也沿用了这个说法。

Netty权威指南中4种IO模型的对比.png-28.7kB

参考:
怎样理解阻塞非阻塞与同步异步的区别?-知乎
深入理解同步/异步与阻塞/非阻塞区别
也谈BIO | NIO | AIO (Java版–转)
网络编程释疑之:同步,异步,阻塞,非阻塞