前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP中的yield与协程(二十节)

PHP中的yield与协程(二十节)

作者头像
老李秀
发布2020-03-02 11:22:53
8570
发布2020-03-02 11:22:53
举报

大家好,我是诚实且憨厚而又不失优雅的老李,我完美地太监了十二天。

我遇到一件比较逗逼的事情,我简单说说你们感受一下,自从我开公众号后文章的难度系数大概是从容易到逐渐变难,阅读量逐渐呈现降低的趋势。现在基本上都维持在每篇大概200阅读量左右,但是上周六我发了一篇关于回京的文章,1076的阅读量显得「鸡立鹤群」,场面也一度十分尴尬...

这个数据让我开始怀疑人生,但在经过了一番痛定思痛以及思考后,我认为是我这篇文章蹭了肺炎的热点流量。很显然,我坚信大家还是一定非常热爱学习热爱读技术文章的:

尽管阅读量一再新低,但是这《PHP网络编程》还是得坚持写下去一直到写完,主要是找不到接盘的英雄。我看了下目录,这本书我已经完成大概四分之三了,这周如果能再猛押一口恒河水,劲头一上来估计这周就能完成了。

众所周知(大概几十个人知道)老李之前是写过关于PHP的yield的,一共写了两篇而是算是上下篇关系,本来还打算写第三篇但是却像快刀斩乱麻般得太监了,并不是因为我懒,而是我发现如果要写好第三篇PHP的yield必须要铺垫一大堆关于IO的基础知识才行,现如今忽如一夜春风来、玉树流光照后庭,条件允许了一切都成熟饱满了,请让我开始复读PHP的yield。

Yield是PHP 5.5之后引入的新功能,其实隔壁家的Python也有这个玩意。有一天你的老板拿着一个内存只有100KB的智能硬件,这个硬件的功能就是不断从1循环到10000,你急不可耐、动手动脚,很快拍了拍油光锃亮的脑袋活生生憋出来了一段代码:

代码语言:javascript
复制
<?php
$start_mem = memory_get_usage();
$arr = range( 1, 10000 );
foreach( $arr as $item ){
  //echo $item.',';
}
$end_mem = memory_get_usage();
echo " use mem : ".( $end_mem - $start_mem ) / 1024.'bytes'.PHP_EOL;

胡粘代码猛如虎,然后一运行成绩负分滚粗:

拿计算器一算516KB内存几乎快是100KB内存的5倍还要多了,你似乎已经听到了老板让你马上去趟办公室办离职手续然后去财务室领完今天工资后赶紧滚蛋而且让你出去就别回来。就在这关键时刻,经常为你公司负责维护植物湿润的老李来了,由于常年费心照顾植物并使其长期保持湿润,老李早早就脑门锃亮,人称谢顶道人。谢顶道人此时正在用手上下猛烈地搓你座位旁边的滴水观音,撇了一眼你的屏幕后说了声:年轻人用yield吧,然后就默默地离开了,只留下流了一地水的滴水观音和萧索的你...

代码语言:javascript
复制
<?php
$start_mem = memory_get_usage();
function yield_range( $start, $end ){
  while( $start <= $end ){
    $start++;
    yield $start;
  }
}
foreach( yield_range( 0, 9999 ) as $item ){
  //echo $item.',';
}
$end_mem = memory_get_usage();
echo " use mem : ".( $end_mem - $start_mem )/1024.'bytes'.PHP_EOL;

一运行,有点儿意思!代码风骚、效率惊人,连TM内存单位都精确到bytes了:

卧槽,这yield是何方妖孽?

首先观摩一下yield_range()「函数」,和传统函数区别就是传统函数中用return关键字结束,而yield_range()「函数」使用yield关键字结束,所以实际上这坨饱含了yield的代码已经不能称之为函数了;而且还有就是普通的函数你调用一次就结束了,代码段中局部变量一次发射完毕,而yield看起来可以调用多次可以保持其中的局部变量的值与状态。

那么这个yield关键字让「函数」究竟返回了什么呢?

代码语言:javascript
复制
<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    $start++;
    yield $start;
  }
}
$rs = yield_range( 1, 100 );
var_dump( $rs );

// 返回如下:
//object(Generator)#1 (0) {
//}

Generator!Generator!Generator!我叫你三声,你敢答应吗?

Generator就是传说中的生成器,毫无疑问这玩意也是跟随着PHP 5.5诞生而诞生的,而且TA实现了Iterator接口,难怪上面demo里能直接对返回结果进行foreach呢~~~既然实现了Iterator接口,那么满足Iterator接口的各种花式骚操作都可以用在Generator上了,比如:

代码语言:javascript
复制
<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    yield $start;
    $start++;
  }
}
$generator = yield_range( 1, 10 );
// valid() current() next() 都是Iterator接口中的方法
while( $generator->valid() ){
  echo $generator->current().PHP_EOL;
  $generator->next();
}

这个yield_range()有点儿神了,TA似乎能记住变量$start数值当前状态,简单说就是第N次调用时候变量$start的值为X,而第N+1次调用的时候变量$start的值为X+1。

你是不是想到了类似于操作系统中进程上下文切换?进程A在某个时刻被CPU停止,然后调度进程B开始跑,然后停止进程B后重新开始跑进程A,那么进程A再次从「就绪态」轮换到「运行态」的时候,一切的一切都还要从上次停止的时候继续(注意是继续)开始,提了裤子不认人?不存在的...只不过说进程上下文切换,说到底是操作系统完成,而且好像也没有什么API接口之类的可以让我们直接使用这个功能,而这个yield似乎在用户态就实现了这个功能,于是这就给了我们一种搞骚操作的一种可能性。

上面的demo里,我们已经接触了Generator生成器的好几个方法,比如valid()、current()、next()等,然后我们再说一个更重要的方法:send()。

代码语言:javascript
复制
<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    $ret = yield $start;
    $start++;
    echo "yield receive : ".$ret.PHP_EOL;
  }
}
$generator = yield_range( 1, 10 );
$generator->send( "外部发送给Generator:".$generator->current() * 10 );

愉快的跑一波儿:

这个send()使得我们拥有另外一个能力:与Generator进行数据交互。此前的demo都是我们从Generator中获取数据,现如今send()方法可以向Generator发射数据,这就叫持枪互射。

上面代码我们xue微改一下,然后我改你们猜,猜下结果好乏?

代码语言:javascript
复制
<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    $ret = yield $start;
    $start++;
    echo "yield receive : ".$ret.PHP_EOL;
  }
}
$generator = yield_range( 1, 10 );
foreach( $generator as $item ){
  $generator->send( "外部发送给Generator:".$generator->current() * 10 );
}

猜好了?我揭锅了昂... ...啦啦啦~~~

很明显,这个一定大概率地诠释了什么叫意外、什么叫惊喜。相对于喜当爹那种意外和惊喜,yield Generator这种惊喜在意外中又带着一丝丝理性逻辑的拷问。

意外与惊喜

人在家中坐,女神忽叫我;

问女神啥事,女神欲还说;

问女何所思,女神掩面泣;

问女何所忆,女神哭啼啼;

我若嫁于你,你愿共伴离?

如若愿嫁我,我一生相许;

女神破涕笑,笑颜恍若桃;

明到你住所,你休要欺我~

我内心荡漾,然表面逞强。

...

..

.

那个,咳咳,不好意思跑题了跑题了,今天先编到这儿,明天我再发yield Generator的第二篇接着写完这首诗,这个即兴发挥有点儿收不住了。

接着说上面demo为啥会出现这种情况呢?最早的那会儿,大概一两年前吧,我一直以为这是一个bug,后来才发现实际上并不是,这个是一个预期的行为,具体说就是当你调用Generator的send()时,会自动触发一次next()。

yield基础使用方法普及完毕,明天第二篇开始应用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能API社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档