问题
今天考察计算机基础知识。
我们知道计算机I/O是指外部设备与存储之间的数据传输,常见的I/O有磁盘IO、网络IO、打印机IO、键盘IO等等。
为了提高CPU的利用率和程序并发处理的能力,产生了 同步/异步IO、阻塞/非阻塞IO,经过排列组合后产生了这样四个编程术语:
(1)【同步阻塞IO】
(2)【同步非阻塞IO】
(3)【异步阻塞IO】
(4)【异步非阻塞IO】
这四个编程术语之间到底有什么本质区别呢?如何正确理解它们?
解析
先回顾一下几个计算机的基础知识:
(1)通常我们写的应用程序是无法直接访问外部设备(IO外设),需要通过操作系统内核才能进行访问。
(2)【应用程序】【操作系统内核】【IO外设】这三者之间的关系和访问流程是这样的(以应用程序从网卡读取数据为例):
A. 【应用程序】通过“系统调用”访问【操作系统内核】;(回想一下,什么是“系统调用”?)
B. 【操作系统内核】通过“驱动调用”访问【IO外设】;(这里给大家扩展一下冯诺依曼体系架构扩展性的知识点:CPU是不知道IO外设的特性的,CPU只认识和执行它的指令集;众多厂商按照指令集提供了五彩十色的各类外设,外设按照统一标准与CPU交互,实现了计算机各种各样的功能,这就是冯诺依曼架构的扩展性 )
C. 【IO外设】拿到用户的数据后,将数据拷贝到【操作系统内核】;(大家还记得 DMA 原理吗?)
D. 【操作系统内核】中的数据拷贝到应用程序内存中。(至此,我们拿到了数据,可以进行业务逻辑处理了)
(3)【同步/异步IO】描述的是 “应用程序”和“操作系统内核”的交互方式,说得通俗一些,就是这两个角色之间消息通信的机制:【同步IO】是“应用程序”占主动,它会一直等待“操作系统内核”的结果;【异步IO】则是“操作系统内核”占主动,它会主动通知“应用程序”;以上面描述的读取网卡数据的流程为例:【同步 /异步IO】是发生在 D 环节,应用程序如果被阻塞了,就是同步IO,未被阻塞时,就是异步IO。
(4)【阻塞/非阻塞IO】描述的是“应用程序”调用“操作系统内核”的操作方式,说得通俗一些,就是调用函数接口的方式:【阻塞IO】是“应用程序”调用函数时在未拿到结果时处于阻塞状态(当前线程会被挂起),【非阻塞IO】是“应用程序”调用函数时不会被挂起,可以继续运行;以上面描述的读取网卡数据的流程为例:【阻塞/非阻塞IO】是发生在 A 环节,这个环节如果应用程序阻塞了,就是阻塞IO,否则就是非阻塞IO。
参考答案
理解了以上基础知识后,就可以回答我们的问题了:
【阻塞IO 和 非阻塞IO】描述的是 “应用程序” 调用 “系统内核”的操作方式,发生在 A 环节;
【同步IO 和 异步IO】描述的是 “应用程序”和“系统内核”的交互方式,发生在 D 环节;
通过排列组合,会形成四个词:【同步阻塞IO】【同步非阻塞IO】【异步阻塞IO】【异步非阻塞IO】;不难思考,【异步阻塞IO】在实际应用中非常少(我都已经阻塞调用了,都已经知道产生数据了,还需要你异步的方式通知我,多此一举呀!),所以提到【异步IO】都会默认为【异步非阻塞IO】。
(1)【同步阻塞IO】: 在客户端调用中比较常见,比如MySQL客户端、Redis客户端等;(还记得 数据库连接 为什么要设计成 同步阻塞的方式吗? 还记得 RPC 和 数据库连接池的区别吗?)
(2)【同步非阻塞IO】:非阻塞IO,线程不会被挂起,比较占用CPU资源,如果只处理一个IO外设,就太浪费了,所以在实际应用中,往往通过这种方式处理多个fd, 即: IO多路复用; 这个方式非常常见,比如:LinuxI系统的 select 模型 和 epoll 模型;另外,Go语言的底层在处理多并发时就是采用的这种方式;
(3)【异步IO】:异步IO的主要工作都交给了 系统内核完成,应用程序比较清爽,理论上讲,这是处理高并发最优雅的解决方案;比如: Node.js, windows系统的IOCP都是异步IO的典型。