接上篇 linux IO —— 多路复用(select/poll/epoll)

阻塞/非阻塞、同步/异步,是两对非常容易弄混淆的名词。不光可能由于对原理的理解不到位而造成混淆;在理解原理后,也有可能因为词义的原因造成混淆,即大家的理解都是没问题的,只是描述的时候出了偏差。

网上分析这两个概念的文章非常多,这里也记录一下我的理解吧,如果理解有误,麻烦轻喷指出…

描述对象

阻塞/非阻塞、同步/异步,描述的都是 一段程序 在进程(也可能是线程,下面统一用进程指代)内的执行状态。

这里加粗的部分值得注意,一个是描述对象,当我们讨论一个状态时,一定要指明被描述的目标对象,否则可能鸡同鸭讲,大家各说各的。

例如,问:一段使用epoll做网络IO的代码 是阻塞的还是非阻塞的,是同步的还是异步的?我认为这样的问题是没有意义的,因为描述的对象不够具体,针对不同的对象也有不同的答案。

阻塞与同步

阻塞IO

从操作系统的角度来讲,IO操作是阻塞的,还是非阻塞的,指的是IO操作会不会阻止 CPU 执行当前进程。

例如阻塞read:当没有可读数据,read操作无法完成时,该操作会将当前进程从CPU的工作队列中移除,不再执行当前进程的任务,直到有数据到来,我们认为它是阻塞的。

image.png

如上图,一次阻塞read的过程,应用进程从调用read,到开始处理数据,中间经历了两个阶段,一个是“等待数据”,另一个是“数据从内核到用户”。这两个阶段中,操作系统会将进程移出CPU的工作队列,阻止CPU执行当前进程的任务,我们称这个read操作是“阻塞”的。

同步

首先说明,“阻塞当前进程”这个说法是比较口语化的,非官方的说法,但是很多人沟通的时候喜欢这样描述,所以单独提出来下。

“阻塞当前进程”即进程在某 一段时间 内持续做这件事情,不能去做其它事情。

例如上面图中的 “等待数据”,“数据从内核到用户”,这两个时间段内,进程都不能去做其它事情,很多时候这种状态会被描述为,进程被阻塞住了。

对于一个线程而言,其实有一种更准确的说法:这个操作是 同步 的。如果将这个阻塞的过程放到另一个线程去执行,那么它就不会占用当前线程的时间了,就应该称它为异步操作。

同步操作还有,sleep、互斥锁等待、while true/自旋锁等待,等等。它们的特点都是会占用进程一段时间,不能做其它事情。但是前两个操作,进程是会进入阻塞状态的,而后两个,则不会进入阻塞状态,所以前面关于“阻塞当前进程”的口语化的说法,是不准确的,分歧点 就在这里。

所以我们能得出结论:对于同一个进程(线程),阻塞操作一定是同步的,同步操作不一定是阻塞的。

非阻塞与异步

非阻塞IO

前面分析了阻塞IO的流程,下面是非阻塞IO的流程。

image.png

非阻塞IO的场景下,“等待数据”阶段,不会再让进程进入阻塞状态,而是定期的去调用read,如果没有数据,则瞬间返回结果 EWOULDBLOCK,不占用 一段时间,这样CPU就可以去做其它事情(例如操作另一个IO,我们称这个read操作是“异步”的。

但是,这里有一点要注意的是,“数据从内核到用户”,进程还是需要等待一会儿CPU将数据从内核缓冲区拷贝到用户缓冲区。这个过程也是非阻塞的,不会让出CPU,但是在此期间进程也是无法执行其它工作的,也就是“同步”的。

非阻塞与异步

从上面的图中可以看到,非阻塞IO分为两个阶段,一个是非阻塞轮询阶段,这个阶段瞬间返回不占用“一段时间”,进程在此期间还可以去做其它事情,是“异步”的;而在第二个复制数据的阶段,这个阶段进程也是非阻塞状态,但是需要等待数据拷贝完成,会占用“一段时间”,尽管这个时间非常短,我们也认为这个过程是“同步”的。

在轮询阶段,如果我们对单个fd使用 while true,去不断轮询结果,这样就会持续占用CPU时间,即使整个操作都是非阻塞的,进程也无法再执行其它工作了,这期间的操作也认为是“同步”的。实际上很少会这样做,都是结合多路复用来将等待的过程异步化的。

那么真正的异步IO模型是什么样的呢?

image.png

如图,和非阻塞IO相比,异步IO不再让用户通过多次调用轮询状态了,而是调用io操作后不进入阻塞,立即返回。数据拷贝阶段,也不再等待内核进行拷贝了,而是后续通过信号、事件等方式,告知进程数据已经ready。在通知之前,进程可以做其它的工作,等待期间不占用进程资源。

所以我们能得出结论:对于同一个进程(线程),非阻塞操作不一定是异步的,但是异步操作一定是非阻塞的。

场景分析

正如第一节“描述对象”中所说,抛开具体的目标对象谈阻塞还是非阻塞,同步还是异步,都是无意义的,可以对下面是几种IO模型分析一下,各个阶段是阻塞的还是非阻塞的,是同步的还是异步的。

image.png

阻塞IO

这个没什么好说的,全程阻塞,全程同步

非阻塞IO

检查阶段是非阻塞的,异步的;数据拷贝阶段非阻塞等待,同步。

IO多路复用

wait阶段阻塞、同步;数据读取阶段由具体的IO操作决定,比如是普通的阻塞/非阻塞IO,在这个阶段都是阻塞的,同步的。

信号驱动IO

有点像自动检查的非阻塞IO,检查阶段非阻塞,异步;数据读取阶段阻塞,同步。

异步IO

全程非阻塞,异步。

总结

对于单个进程(线程):

特性 同步 异步
阻塞 ①阻塞IO全程②多路复用wait③信号驱动IO拷贝节点 不存在
非阻塞 非阻塞IO拷贝阶段 非阻塞IO等待阶段、异步IO

参考文章:

☞ 参与评论