首先是,这是我第一次把公众号文章复制粘贴到sf.gg来。php
其次是,好久好久以前,我挖了一个yield的一个坑,本身挖的坑本身填,否则早晚会把本身埋掉。html
最后是,若是想看以前那个坑,请发送“yield”给文章末尾的公众号,我开通了高大上的自动回复功能,稀罕地不得了!node
PS:那篇文章中在最后我犯了一个错误,误下了一个结论:foreach中不能使用send并猜想这是PHP的bug,实际上并非,真实的缘由粗暴简单的理解就是send会让生成器继续执行一次致使。这件事情告诉咱们:git
除了装逼以外,甩锅也是有打脸风险的
那篇坑里,内容和你能在百毒上搜索到的大多数文章都是差很少的,不过我那篇坑标题起得好:《yield是个什么玩意(上)》,也就是暗示你们还有下篇,因此起标题也是须要必定技术含量的。github
我坚信,在座的各位辣鸡在看完上篇坑文后最想说的注定是泰迪熊这句话(这是文化属性,不以各位的意志而转移):json
回到今天主旨上来,强调几点:服务器
多线程和多进程都是操做系统参与的调度,而协程是用户自主实现的调度,协程的关键点其实是“用户层实现自主调度”,大概有“翻身农奴把歌唱”的意思。微信
下面我经过一坨代码来体会一把“翻身农奴”,大家感觉一下:网络
<?php function gen1() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN1 : {$i}".PHP_EOL; // sleep没啥意思,主要就是运行时候给你一种切实的调度感,你懂么 // 就是那种“你看!你看!尼玛,我调度了!卧槽” sleep( 1 ); // 这句很关键,表示本身主动让出CPU,我不下地狱谁下地狱 yield; } } function gen2() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN2 : {$i}".PHP_EOL; // sleep没啥意思,主要就是运行时候给你一种切实的调度感,你懂么 // 就是那种“你看!你看!尼玛,我调度了!卧槽” sleep( 1 ); // 这句很关键,表示本身主动让出CPU,我不下地狱谁下地狱 yield; } } $task1 = gen1(); $task2 = gen2(); while( true ) { // 首先我运行task1,而后task1主动下了地狱 echo $task1->current(); // 这会儿我可让task2介入进来了 echo $task2->current(); // task1恢复中断 $task1->next(); // task2恢复中断 $task2->next(); }
上面代码执行结果以下图:多线程
虽然我话都说到这里了,可是确定仍是有人get不到“因此,到底发生了什么?”。你要知道,若是function gen1和function gen2中没有yield,而是普通函数,你是没法中断其中的for循环的,诸以下面这样的代码:
<?php function gen1() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN1 : {$i}".PHP_EOL; sleep( 1 ); } } function gen2() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN2 : {$i}".PHP_EOL; } } gen1(); gen2(); // 看这里,看这里,看这里! // 上面的代码一旦运行,必定是先运行完gen1函数中的for循环 // 其次才能运行完gen2函数中的for循环,绝对不会出现 // gen1和gen2交叉运行这种状况
我彷佛已然精通了yield
写到这里后我也开始蹩了,和以往的憋了三天蹦不出来个屁有所不一样,我此次蹩出了一个比较典型的应用场景:curl。下面咱们基于上面那坨辣鸡代码将gen1修改成一个耗时curl网络请求,gen2将向一个文本文件中写内容,咱们的目的就是在耗时的curl开始后主动让出CPU,让gen2去写文件,以实现CPU的最大化利用。
<?php $ch1 = curl_init(); // 这个地址中的php,我故意sleep了5秒钟,而后输出一坨json curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" ); curl_setopt( $ch1, CURLOPT_HEADER, 0 ); $mh = curl_multi_init(); curl_multi_add_handle( $mh, $ch1 ); function gen1( $mh, $ch1 ) { do { $mrc = curl_multi_exec( $mh, $running ); // 请求发出后,让出cpu yield; } while( $running > 0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false; } function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND ); yield; } } $gen1 = gen1( $mh, $ch1 ); $gen2 = gen2(); while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->next(); $gen2->next(); }
上面的代码,运行之后,咱们再等待curl发起请求的5秒钟内,同时能够完成文件写入功能,若是换作平时的PHP程序,就只能是先阻塞等待curl拿到结果后才能完成文件写入。
文章太长,就像“老太太的裹脚布同样,又臭又长”,因此,最后再对代码作个极小幅度的改动就收尾不写了!
<?php $ch1 = curl_init(); // 这个地址中的php,我故意sleep了5秒钟,而后输出一坨json curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" ); curl_setopt( $ch1, CURLOPT_HEADER, 0 ); $mh = curl_multi_init(); curl_multi_add_handle( $mh, $ch1 ); function gen1( $mh, $ch1 ) { do { $mrc = curl_multi_exec( $mh, $running ); // 请求发出后,让出cpu $rs = yield; echo "外部发送数据{$rs}".PHP_EOL; } while( $running > 0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false; } function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND ); $rs = yield; echo "外部发送数据{$rs}".PHP_EOL; } } $gen1 = gen1( $mh, $ch1 ); $gen2 = gen2(); while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->send("gen1"); $gen2->send("gen2"); }
咱们修改了内容:
将$gen1->next()修改为了$gen1->send("gen1")
在function gen1中yield有了返回值,而且将返回值打印出来
这件事情告诉咱们:yield和send,是能够双向通讯的,同时告诉咱们send能够用来恢复原来中断的代码,并且在恢复中断的同时能够携带信息回去。写到这里,你是否是以为这玩意的可利用价值是否是比原来高点儿了?
我知道,有人确定叨叨了:“老李,你代码特么写的真是辣鸡啊!你以前保证过了的 --- 只在公司生产环境写辣鸡代码的。可你看看你这辣鸡光环到笼罩都到demo里了,你连demo都不放过了!你怎么说?!”。兄dei,“又不是不能用”。并且我告诉你,上面这点儿curl demo来说明白yield仍是不够的,后面还有两三篇yield呢,照样是烂代码恶心死你,爱看不看。我劝你心放宽,你想一想你这么烂的代码都经历了,还有什么不能经历的?
文章最后补个小故事:其实yield是PHP 5.5就已经添加进来了,这个模块的做者叫作Nikita Popov,网络上的名称是Nikic。咱们知道PHP7这一代主力是惠新宸,下一代PHP主力就是Nikic了。早在2012年,Nikic就发表了一篇关于PHP yield多任务的文章,连接我贴出来你们共赏一下 --- http://nikic.github.io/2012/1...