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

PHP评测之协程

时间:2023-03-29 19:42:08 PHP

转载请注明文章出处:https://tlanyan.me/php-review...PHP评测系列目录PH??P基础web请求cookieweb响应session数据库操作加密解密composer自创Composer包发送邮件IO流程Socket编程多进程编程执行流程及相关概念PHP从5.5开始引入了生成器,基于生成器可以实现协程编程。本文从生成器的回顾开始,然后过渡到协程编程。yield和生成器生成器是一种实现迭代器接口的数据类型。生成器实例无法通过new获取,也没有获取生成器实例的静态方法。获取生成器实例的唯一方法是调用生成器函数(包含yield关键字的函数)。调用生成器函数直接返回一个生成器对象,生成器运行时函数内的代码开始执行。下面进入代码,直观感受一下yield和generator:#generator1.phpfunctionfoo(){exit('exitscriptwhengeneratorruns.');yield;}$gen=foo();var_dump($gen);$gen->current();echo'unreachablecode!';#Executionresultobject(Generator)#1(0){}生成器运行时退出脚本.foo函数包含yield关键字并变成生成器函数。调用foo不会执行函数体中的任何代码,而是返回一个生成器实例。生成器运行后,foo函数内的代码开始执行,脚本结束。顾名思义,生成器可用于生成数据。只是它生成数据的方式与其他函数不同:生成器通过yield而不是return返回数据;yield返回数据后,生成器函数不会被销毁,只是暂停运行,以后可以从暂停点恢复;生成器运行一次,(只)返回一个数据,运行多次后返回多个数据;如果不调用生成器获取数据,生成器中的代码就会静止不动,所谓动态次就是指生成器生成的数据看起来。生成器实现迭代器接口,可以使用foreach循环或者手动current/next/valid获取生成器数据。下面的代码演示了数据的生成和遍历:#generator2.phpfunctionfoo(){#returnkey-valuepairdatayield"key1"=>"value1";$计数=0;while($count<5){#返回值,key自动生成yield$count;++$计数;}#不返回值,相当于返回nullyield;}#手动获取generator数据$gen=foo();while($gen->valid()){fwrite(STDOUT,"key:{$gen->key()},值:{$gen->current()}\n");$gen->next();}#foreach遍历数据fwrite(STDOUT,"\ndatafromforeach\n");foreach(foo()as$key=>$value){fwrite(STDOUT,"key:$key,value:$value\n");}yieldyield关键字是生成器的核心,它将普通函数异化(进化)为生成器函数。yield有“放弃”的意思。当程序执行到yield语句时,会暂停执行,让出CPU并将控制权交还给调用者,下次执行时从中断点继续执行。当控制返回给调用者时,yield语句可以将一个值返回给调用者。generator2.php脚本演示了yield返回值的三种形式:yield$key=>$value:返回数据的key和value;yield$value:返回数据,key由系统分配;yield:返回空值,key由系统Allocation分配;yield允许函数随时暂停,继续执行,并将数据返回给调用者。如果需要外部数据继续执行,这个工作由生成器的send函数提供:出现在yield左边的变量会接收到send传过来的值。看一个使用send函数的常见例子:functionlogger(string$filename){$fd=fopen($filename,'w+');while($msg=yield){fwrite($fd,date('Y-m-dH:i:s').':'.$msg.PHP_EOL);}fclose($fd);}$logger=logger('log.txt');$logger->send('programstarts!');//做一些事情$logger->send('programends!');send启用生成器与外部之间的双向数据通信:yield返回数据;send为继续操作提供支持数据。由于send允许generator继续执行,这个行为类似于iterator的next接口,相当于send(null)。$string的其他表达式=yield$data;PHP7之前是非法的,需要加括号:$string=(yield$data);PHP5生成器函数不能返回值,PHP7之后可以返回值,并通过生成器的getReturn获取返回值。具体可以参考返回值的RFC:https://wiki.php.net/rfc/gene...;PHP7增加了yieldfrom语法,实现了生成器委托。详情请参考其RFC:https://wiki。php.net/rfc/基因...;生成器是一个单向迭代器,启动后不能调用rewind。总结与其他迭代器相比,生成器具有性能开销低、易于编码的特点。它的作用主要体现在三个方面:数据生成(producer),通过yield返回数据;数据消费(consumer),从send消费数据;协程的实现。关于PHP中的生成器和基本用法,推荐阅读2gua大佬的博文:PHP生成器,生动有趣,通俗易懂。协程是一个可以随时中断和恢复的子程序。yield关键字让函数具有这种能力,因此可以用于协程编程。进程、线程、协程线程都属于进程,一个进程可以有多个线程。进程是计算机分配资源的最小单位,线程是计算机调度执行的最小单位。进程和线程都是由操作系统调度的。协程可以看作是“用户态的线程”,需要用户程序来实现调度。线程和进程被操作系统调度以“抢占模式”交替运行,协程主动让出CPU以“协商模式”交替运行。协程非常轻量级。协程切换不涉及线程切换。执行效率高。数量越大,越能体现协程的优势。生成器与协程生成器实现的协程是无栈协程(stacklesscoroutine),即生成器函数只有一个函数框架,在运行时附加到调用者的栈上执行。与强大的stackful协程不同,generator挂起后无法控制程序的运行方向,只能被动地将控制权交还给caller;生成器只能中断自己,不能中断整个协程。当然generator的优点是效率高(挂起时只保存程序计数器),实现简单。协程编程说到PHP协程编程,相信大多数人都看过鸟哥转载(翻译)的这篇博文:在PHP中使用协程实现多任务调度。原作者nikic是PHP的核心开发者,generator函数的发起者和实现者。如果想深入了解生成器及其协程编程,nikic关于生成器的RFC和鸟哥网站上的文章一定要看。nikic文章的generator部分很容易理解。看完以后用yield写一个类似xrange的函数肯定毫无压力。为什么一进入协程就觉得有点懵?先看一下基于生成器的协程的工作方式:协程协同工作,即协程主动让出CPU,交替实现多任务运行(即并发多任务,但不是并行);一个generator可以看作是一个协程,执行到yield语句,放弃CPU控制权返回给调用者,调用者继续执行其他协程或其他代码。看懂鸟哥博客的难点吧。协程非常轻量级,一个系统中可以同时存在上千个协程(生成器)。操作系统不对协程进行调度,安排协程执行的工作就落在了开发者身上。鸟哥的文章协程部分有些人看不懂,因为上面说协程编程比较少(写协程主要是写generator函数),但是自己用笔墨实现了一个协程调度器(scheduler或者kernel).:模拟操作系统,对所有协程进行公平调度。PHP开发的一般思路是:我写了这些代码,PHP引擎会调用我的代码得到预期的结果。而协程编程不仅要写代码来干活,还要写代码来引导这些代码什么时候干活。没有把握好作者的思路,自然难懂。需要自调度,这是生成器协程相对于原生协程(async/await形式)的劣势。现在您知道协程是什么,它可以用来做什么?协程自己放弃CPU来协同高效使用CPU。当然,放弃的时机应该是程序被阻塞的时候。程序会阻塞在哪里?用户态代码很少被阻塞,阻塞主要是系统调用。系统调用的主体是IO,所以协程的主要应用场景是网络编程。为了使程序具有高性能和高并发性,程序应该是异步执行的,不阻塞。由于它是异步执行的,因此需要通知和回调。编写回调函数无法避免“回调地狱”的问题:代码可读性差,程序执行过程分散在层层回调函数中。解决回调地狱的方法主要有两种:Promise和协程。协程可以同步编写代码,推荐用于高性能网络编程(IO密集型)。回头看看PHP中的协程编程。协程编程在PHP中是基于生成器实现的,优先使用RecoilPHP、Amp等协程框架。这些框架都已经写好了调度器,生成器函数直接写在上面,内核会自动调度执行(如果你想让一个函数以协程方式调度执行,在函数体中加上yield即可).如果不想使用yield方式进行协程编程,推荐swoole或其衍生框架,可以实现类似golang的协程编程体验,享受PHP的开发效率。想要利用原生态做PHP协程编程,类似鸟哥博客里的调度器是必不可少的。调度器调度协程的执行,协程中断后控制权返回给调度器。所以调度器应该一直在主(事件)循环中,也就是说CPU不是在执行协程,它应该是在执行调度器的代码。当没有协程在运行时,调度器应该阻塞自己以避免消耗CPU(鸟哥的博客中使用了内置的select系统调用),等待事件到来,然后再执行对应的协程。在程序运行过程中,除了调度器的阻塞,协程在运行过程中不应该调用阻塞API。总结在协程编程中,yield的主要作用是转移控制,而不用担心它的返回值(基本上yield返回的值会在下次执行时直接发送)。重点应该放在控制权转移的时间以及协程的工作方式上。另外需要说明的是,协程与异步关系不大,需要依赖运行环境的支持。常规的PHP运行环境,即使使用promise/coroutine,依然是同步阻塞。再牛逼的协程框架,sleep也不好用。打个比方,JavaScript是异步和非阻塞的,即使它不使用promise/async技术。通过generators和Promise可以实现类似于await的协程编程。Github上有很多相关代码,本文不再赘述。小结本文首先介绍了生成器的概念,重点介绍了yield的用法和生成器的接口。协程部分简单说一下协程的原理和PHP协程编程应该注意的事项。感谢阅读,欢迎指正!参考http://php.net/manual/zh/lang...http://php.net/manual/zh/clas...https://wiki.php.net/rfc/gene...https://wiki.php.net/rfc/gene...https://zhuanlan.zhihu.com/p/...http://www.laruence.com/2015/...https://中等.com/async-php/...https://blog.kghost.info/2011...