当前位置: 首页 > 后端技术 > PHP

Swoole协程之旅——第一篇

时间:2023-03-29 15:16:47 PHP

写在最上面Swoole协程经历了几个里程碑。我们需要在前进的道路上不断总结和回顾自己的发展历程。俗话说,温故而知新。本系列文章将分为协程之旅前、中、后三个部分。第一部分主要介绍协程的概念以及Swoole协程几个版本的主要方案和技术;中间部分主要深入分析了PHP的原理和实现;第二部分主要是对coroutine4.x的实现进行补充和分析。正式启动协程的软文是什么?这个概念其实很早就出现了。摘自wiki中的一段:根据DonaldKnuth的说法,协同程序一词是MelvinConway于1958年创造的,当时他将其应用于汇编程序的构建。协程的第一个发表的解释出现较晚,在1963年。协程的历史比C语言更长。其概念是协程是一种子程序,可以通过yield来转移程序的控制权。协程之间的关系不是调用者和被调用者之间的关系,而是相互对称、平等的。协程完全由用户态程序控制,因此也称为用户态线程。协程由用户非抢占式地调度,而不是操作系统。正因为如此,没有系统调度上下文切换的开销,协程是轻量级、高效和快速的。(大部分都是非抢占式的,但是,比如golang在1.4也加入了抢占式调度,其中一个协程死循环,这样其他协程就不会被饿死。需要在必要的时候让出CPU,Swoole在V4.3.2中增加了此功能)。协程这几年这么火,很大一部分原因是因为golang在国内的火爆和快速发展,受到了很多开发者的喜爱。目前支持协程的语言有很多,比如golang、lua、python、c#、javascript等,你也可以用c/c++用很短的代码创建一个协程模型。当然,PHP也有自己的协程实现,即生成器,这里不做讨论。Swoole1.xSwoole最初作为一个高性能的网络通信引擎进入大家的视线。Swoole1.x的编码主要是异步回调方法。虽然性能非常高效,但是很多开发者会发现,随着项目复杂度的增加,用异步回调的方式写业务代码有悖于人的正常思维,尤其是多层嵌套回调时,不仅开发和维护成本会成倍增加,同时出错的概率也会大大增加。大家理想的编码方式是:同步编码以获得异步非阻塞性能。所以Swoole很早就开始探索协程了。最初的协程版本是基于PHP生成器GeneratorsYield实现的。可以参考PHP高手Nikita早期博客关于协程的介绍。PHP和Swoole的事件驱动组合可以参考腾讯团队开源的TSF框架。我们也在很多生产项目中使用了这个框架。真正让大家感受到用同步编程的方式编写异步代码的乐趣。然而,现实总是残酷的,但这种方法有几个致命的缺点:所有自愿投降的逻辑都需要yield关键字。这会给程序员带来极大的犯错概率,导致大家对协程的理解转向对Generators语法原理的理解。由于语法与老项目不兼容,改造老项目的工程复杂度和成本巨大。这使得无论是新项目还是旧项目都无法轻松使用。Swoole2.x2.x之后的协程都是基于内核原生协程,没有yield关键字。2.0版本是一个非常重要的里程碑。实现了PHP的栈管理,在协程创建、切换、结束时,深入zend内核对PHP栈进行操作。Swoole的文档中也介绍了很多关于各个版本实现的细节。本文仅简单介绍各个版本的协程驱动技术。本机协程管理PHP堆栈。后面我们会单独出一篇文章深入分析PHP栈的管理和切换。2.x主要使用setjmp/longjmp来实现coroutines。许多C项目主要使用这种方法来实现try-catch-finally。也可以参考Zend内核的使用方法。setjmp第一次调用的返回值为0,longjmp跳转时,setjmp的返回值就是传递给longjmp的值。setjmp/longjmp只有控制流跳跃的能力。虽然PC和栈指针可以恢复,但是栈帧无法恢复,所以会出现很多问题。比如在longjmp的时候,setjmp的作用域已经退出,此时的栈帧已经被销毁。然后发生未定义的行为。假设有这样一个调用链:func0()->func1()->...->funcN()只有setjmpinfunc{i}(),andlongjmpinfunc{i+k}(),行为该计划的是可预测的。Swoole3.x3.x是一个生命周期很短的版本。主要借鉴了fiber-ext项目,利用了PHP7的VM中断机制。转和函数调用等)检查标志位,如果命中则可以执行相应的钩子函数来切换vm的栈,进而实现协程。虽然我们已经完全实现了协程的功能,但是因为相比2.x并没有太大的进步,我们会在后续的文章中做进一步的分析,所以我们放弃了这个版本,直接进入了4.x版本迭代。Swoole4.x4.x协程是目前Swoole的协程版本。吸取上一版本的不足和问题,引入PHP+C双栈管理维护,完美支持PHP各种语法。具体分析我们会放在系列文章的最后。End协程之旅的第一部分就结束了。在下一篇文章中,我们将深入分析Swoole原生协程的PHP部分在Zend中的实现。