几个经常容易混淆的概念。特别是像异步与非阻塞,在某些情况下这两个术语常常混用。下面就我谈谈自己对这四个概念的理解,也解释了异步与非阻塞这两个词为何会形影不离。希望对这些概念迷惑,学习Python协程、并发相关的朋友有所帮助。
探究事物的基本逻辑
当我们探讨事物的时候,一定要先界定清楚事物的时空界限以及其基本定义,也要先界定清楚探讨的事物/问题的级别或层次(在阿驹看来,世界上的任何东西都是具有分层/分级的,当我们层次分明的时候,很多架构就显得清晰明了)。
就像参考系不确定,怎敢断言一个物体是处于运动态还是静止态?而且当我们提升层次(抽象)和降低层次(分解)来分析问题的时候,一定要回到当初研究的级别上。否则逻辑都有问题,结论还怎可正确。就像研究人,可以分解到各个器官,甚至是各种细胞来研究人,但你最终要回到人本身这个角度,你不能说细胞的特性就是人性。
就讨论计算机程序设计与编写的角度来说,问题的级别与层次是什么呢?最基本的就是要确定我们讨论的是一条CPU指令?一个函数?一个类?一个模块?一个服务?一个线程?一个进程?一个操作系统?…还是你要下的结论与任何级别的程序都通用?在不同的级别下,有些概念是存在的,有些概念是不存在的。
还有就是界定清楚时空界限。比如常常谈论程序A与B的性能孰高孰低,架构其孰好孰坏等等。谈论者往往都会站在自己的主观角度,给程序套上自己常接触到的环境,比如没做过分布式应用的,拿着分布式应用在单体应用运行环境和场景中去比。就算功能、架构都定位相同的程序,也得划清一个界限来比较,比如执行时效?生存周期?部署规模?吞吐量?
在我们谈论阻塞、非阻塞、同步、异步这几个概念的时候,也得先划定基本的前提条件,要在同一个级别、同样的时空里来探讨。后文,用“程序单元”这个说法,站在不同的级别,程序单元是不同的。当没有特别说明的时候,你可以认为程序单元就是你心中认为的那一个,当说某程序单元的上一级的时候,你心里要知道,如果先前认为的程序单元是线程,那它的上一级可以是进程,诸如此类。
阻塞
阻塞与非阻塞的概念是针对一个程序单元自身而言。
如果一个程序单元的某个操作,在等待这个操作完成的过程中程序单元它自身无法继续进行下去做别的事情,那就称这个程序单元在等待该操作时是阻塞的。
这可以是被阻塞的程序单元依赖于别的程序单元,别的程序单元完成它的要求中,它无法继续做任何其他事。 也可以是对硬件设备的依赖(其实在程序看来是对更底层驱动程序的依赖)。常见的阻塞形式有网络I/O阻塞,磁盘I/O阻塞,用户输入阻塞,CPU阻塞等等。
站在被阻塞的程序单元自身来讲,是它消耗了时间等待某事情的结果而发生了阻塞。所以,在说到阻塞的时候,心里就需明确知道,完整的一句描述应该是“某程序因等待某操作的结果而阻塞”。 比如一个文件操作函数,因为要等待磁盘I/O拿到数据,那么就称为该文件操作函数因等待磁盘I/O的结果而发生了阻塞。
非阻塞
非阻塞就是阻塞的反面。即是如果一个程序单元的某个操作,在等待这个操作完成的过程中程序单元它自身可以继续进行下去做别的事情,那就称这个程序单元在等待该操作时是非阻塞的。
这里我们就会看到非阻塞并不是在任何程序级别、任何情况下都是存在的。例如在单个函数的级别,上一行是向磁盘索取文件,下一行是对文件内容进行运算,那么当磁盘I/O未有结果时,是无法继续下一行的,此时这个函数是不可能存在非阻塞的状态的。
只有当程序单元高到了一定级别,它可以囊括独立的子程序单元,它才可能有非阻塞状态的存在。因为阻塞的操作都在子程序单元中,阻塞的是子程序单元而不会阻塞它本身。例如一个单进程多线程的文件操作程序,某个线程在操作一个文件时阻塞了,而别的线程还可以继续运行下去,该进程本身还是运行态而非阻塞态,所以该文件操作程序是非阻塞的。
由上可知,在计算机程序的世界里,阻塞是绝对的,非阻塞是相对的。
同步
同步异步的概念是针对至少两个程序单元而言(可以是同级别的,也可以是不同级别的程序单元)。同,一致;异,不一致;只有一个程序单元的世界里,没有别的和它对比,谈何一致或不一致?
多个程序单元之间,通过某种方式通信进行了协调,使它们在相同的时间点的行为或目标一致就称为同步。
在生活中同步的例子就是三军仪仗队行进过程中,他们通过瞄排头兵和身边队友的这种行为来协调自己的动作与他们一致,这就是同步。国旗手听到国歌的某个旋律(信号)就开始撒开国旗并往上升,通过这种行为让国旗的上升与国歌的旋律同步。
可见,同步的重点在于多方之间有信息传递并以此协调一致完成共同目的,而不在于非要多方完成一模一样的事情。
异步
毫不相干的程序单元之间肯定是异步的,多个相干的程序单元之间虽然有某种方式的通信,但过程中无需协调一致也能完成共同目标就是异步的。
我们会发现,真正意义上的异步是比较少的。而我们常见的技术文档中所提到的异步,是指多个相干的程序单元,其中一个对另一个有依赖,发起请求的程序单元不必等待另一个完成所有的需求就得到了一个临时的返回结果,而请求方真正需要的数据是稍后再返回给请求方。为了解耦程序单元之间的直接关系,所以常常引入了第三者,即是所谓的异步框架或者异步机制。
常见的异步机制有回调、事件循环、信号量等,它们也常常会相互结合使用。
由上述可知,现在大多数文档或者大家所说的异步,其主要目的其实是为了让请求方不必为了该次请求而阻塞以提高请求方的工作效率,所以非阻塞与异步两词就如孪生兄弟般形影不离甚至出现了相互代替的现象。 我们也可以得知,绝大多数时候,程序单元的级别能够囊括独立子程序单元的情况下,才会有所谓的异步,比如要借助多协程、多线程、多进程来实现异步程序。
总结
我们在理解以上四个概念的时候,一定别以固化的思维去理解,别一提到上述概念想到的就是I/O,就是单体应用,甚至是还将多个层次混为一谈,那是不可的。一个程序单元可以在某些情况下是阻塞的,可以在某些情况下是非阻塞的,可以在某些时候是同步的,可以在某些时候是异步的,这都没有确切的定性。
根据上文的解释,大家还可以自行理解一下“异步阻塞”、“异步非阻塞”、“同步非阻塞”、“同步阻塞”这四种模式各自是怎样的情景?为了完成同样的功能,针对不同的应用目的,选择哪种模式才是最合适的?哪些模式是完全没必要存在于任何程序中的?哪些模式是可以被任何程序都可以采用的?在应对大规模并发的时候,这四种模式应该各自如何扩展才能应对挑战?
如果上一段落的几个问题都能思考明白,得到清晰准确的结果,那对本文所述的四个概念算是理解透彻了。
如果你对本文所述的内容有不同的见解,欢迎留言,共同探讨,共同进步。