引入
多任务处理,或者说并发,一直是人对于让计算机同时执行更多任务的普遍性需求。而多任务处理的方案也经历了多次变迁。解耦是我们想达成既要好处和又要避免局限性的一般性方法。从协程到线程再到协程,让我们从解耦的视角来回顾下整个历程。
协程出现——系统级别协作式多任务
协程的出现,实现了单处理器核心计算与单任务处理的解耦,使多任务处理成为可能。
如Win16模式中整个Windows系统的「事件循环」模式,要求每个应用不断的去做
1 2 3 4 |
GetMessage TranslgeMessage DispatchMessage |
这样的循环处理。
线程出现——系统级别抢占式多任务
线程的出现,实现了多任务处理中,逻辑处理与调度处理的解耦。
开发者不再需要关注如何进行调度,关注点可以集中在自身的逻辑处理上。降低了开发者的心智负担,从而提高了效率。
另外,随着多处理器与多核心的出现与普及,多线程“正好”在当时解决了多核心利用的可能性问题。这本质上是一种“误用”。因为线程的出现目的更多是为了在操作系统级别为应用开发提供一种无需关注多任务调度的抽象,从其概念和API设计就可以看出,其诞生时原生并未(也不可能)考虑操作系统以下级别的指令重排等问题,后续的出现的不同粒度的线程同步机制基本可以看作补丁,显著增加了多线程在多核下使用的复杂性。这种复杂性,正是由于线程的最初设计目的并不是利用多核计算能力导致的。
协程再现——库、语言、系统级别的协作式多任务
协程的再现,实现了多任务处理中,同步阻塞IO逻辑与出让CPU计算资源的解耦。
随着IO延迟的降低(低于20ms甚至到2ms),CPU的时间片20ms与IO响应速度不再匹配,符合人认知逻辑的线性代码组织方式的CPU的线程切换变多因此成本变高,不再能达到较高的CPU利用率;同时IO型线程在有计算型线程的竞争下,由于反复等待不再能充分利用IO资源。
开发者迫不得已引入了通过「IO复用」和「异步IO」模式,如epoll和aio。对于业务流程时序相对简单但性能需求高的场景来说,这两种开发模式具有高可行性,如crtmpserver和nginx。但对于业务流程相对复杂且性能需求高的场景来说,这两种模式的非线性的代码组织方式极大的增加了开发者的心智负担,开发变成了一场噩梦。在人力成本是公司瓶颈的现实下,甚至包括性能都要向「开发者友好」的做妥协。
在这种情况下,协程重新兴起。不管是从库级别的state-threads,语言级别的golang,还是近期系统级别的linux kernel coroutine,都在支持开发者使用线性逻辑做开发,顺畅的做IO和逻辑密集型的开发,提高了人效,降低了成本。在更有效的利用CPU时间片和协程间切换局部性更高导致缓存更有效的加持下,获得了更好的性能,也降低了成本。
总结
回看多任务处理的历史,确确实实是一个螺旋式上升的过程。当我们在做多任务处理到时候,不要仅仅着眼于在代码层面如何能实现,这是最低的要求。而是要着眼于如何解决当前面临的主要矛盾,包括但不限于人员成本,业务发展,行业领域特点等。从历史的轨迹看,以终为始,把握主要矛盾,解决主要矛盾,才能更好的实现多任务处理。
参考
MS-DOS 4.0 (multitasking) – Wikipedia