设为首页 收藏本站
查看: 873|回复: 0

[经验分享] PHP的生成器、yield和协程

[复制链接]

尚未签到

发表于 2017-12-29 14:33:30 | 显示全部楼层 |阅读模式
PHP的生成器、yield和协程
  虽然之前就接触了PHP的yield关键字和与之对应的生成器,但是一直没有场景去使用它,就一直没有对它上心的研究。不过公司的框架是基于php的协程实现,觉得有必要深入的瞅瞅了。
  由于之前对于生成器接触不多,后来也是在看了鸟哥的介绍在PHP中使用协程实现多任务调度才有所了解。下面也只是说说我的理解。

迭代和迭代器
  在了解生成器之前我们先来看一下迭代器和迭代。迭代是指反复执行一个过程,每执行一次叫做迭代一次。比如普通的遍历便是迭代:
$arr = [1, 2, 3, 4, 5];  

  
foreach($arr as $key => $value) {
  echo $key . ' => ' . $value . "\n";
  
}
  我们可以看到通过foreach对数组遍历并迭代输出其内容。在foreach内部,每次迭代都会将当前的元素的值赋给$value并将数组的指针移动指向下一个元素为下一次迭代坐准备,从而实现顺序遍历。像这样能够让外部的函数迭代自己内部数据的接口就是迭代器接口,对应的那个被迭代的自己就是迭代器对象。
  PHP提供了统一的迭代器接口:
Iterator extends Traversable {  

  // 返回当前的元素
  abstract public mixed current(void)
  // 返回当前元素的键
  abstract public scalar key(void)
  // 向下移动到下一个元素
  abstract public void next(void)
  // 返回到迭代器的第一个元素
  abstract public void rewind(void)
  // 检查当前位置是否有效
  abstract public boolean valid(void)
  
}
  通过实现Iterator接口,我们可以自行的决定如何遍历对象。比如通过实现Iterator接口我们可以观察迭代器的调用顺序。
  
class MyIterator implements Iterator {
  private $position = 0;
  private $arr = [
  'first', 'second', 'third',
  ];
  public function __construct() {
  $this->position = 0;
  }
  public function rewind() {
  var_dump(__METHOD__);
  $this->position = 0;
  }
  public function current() {
  var_dump(__METHOD__);
  return $this->arr[$this->position];
  }
  public function key() {
  var_dump(__METHOD__);
  return $this->position;
  }
  public function next() {
  var_dump(__METHOD__);
  ++$this->position;
  }
  public function valid() {
  var_dump(__METHOD__);
  return isset($this->arr[$this->position]);
  }
  
}
  

  
$it = new MyIterator();
  

  
foreach($it as $key => $value) {
  echo "\n";
  var_dump($key, $value);
  
}
  通过这个例子能够清楚的看到了foreach循环中调用的顺序。从例子也能看出通过迭代器能够将一个普通的对象转化为一个可被遍历的对象。这在有些时候,能够将一个普通的UsersInfo对象转化为一个可以遍历的对象,那么就不需要通过UsersInfo::getAllUser()获取一个数组然后遍历数组,而且还可以在对象中对数据进行预处理。

yield和生成器
  相比较迭代器,生成器提供了一种更容易的方法来实现简单的对象迭代,性能开销和复杂性都大大降低。
  一个生成器函数看起来像一个普通的函数,不同的是普通函数返回一个值,而一个生成可以yield生成许多它所需要的值,并且每一次的生成返回值只是暂停当前的执行状态,当下次调用生成器函数时,PHP会从上次暂停的状态继续执行下去。
  我们在使用生成器的时候可以像关联数组那样指定一个键名对应生成的值。如下生成一个键值对与定义一个关联数组相似。
  
function xrange($start, $limit, $step = 1) {
  for ($i = $start, $j = 0; $i <= $limit; $i += $step, $j++) {
  // 给予键值
  yield $j => $i;
  }
  
}
  

  
$xrange = xrange(1, 10, 2);
  
foreach ($xrange as $key => $value) {
  echo $key . ' => ' . $value . "\n";
  
}
  更多的生成器语法可以参见生成器语法
  实际上生成器函数返回的是一个Generator对象,这个对象不能通过new实例化,并且实现了Iterator接口。
  
Generator implements Iterator {
  public mixed current(void)
  public mixed key(void)
  public void next(void)
  public void rewind(void)
  // 向生成器传入一个值
  public mixed send(mixed $value)
  public void throw(Exception $exception)
  public bool valid(void)
  // 序列化回调
  public void __wakeup(void)
  
}
  可以看到出了实现Iterator的接口之外Generator还添加了send方法,用来向生成器传入一个值,并且当做yield表达式的结果,然后继续执行生成器,直到遇到下一个yield后会再次停住。
function printer() {  while(true) {
  echo 'receive: ' . yield . "\n";
  }
  
}
  

  
$printer = printer();
  
$printer->send('Hello');
  
$printer->send('world');
  以上的例子会输出:
  

receive: Hello  
receive: world
  

  在上面的例子中,经过第一个send()方法,yield表达式的值变为Hello,之后执行echo语句,输出第一条结果receive: Hello,输出完毕后继续执行到第二个yield处,只不过当前的语句没有执行到底,不会执行输出。如果将例子改改就能够看出来yield的继续执行到哪里。
  
function printer() {
  $i = 1;
  while(true) {
  echo 'this is the yield ' . $i . "\n";
  echo 'receive: ' . yield . "\n";
  $i++;
  }
  
}
  

  
$printer = printer();
  
$printer->send('Hello');
  
$printer->send('world');
  这次的输出便会变为:
  

this is the yield 1  
receive: hello
  
this is the yield 2
  
receive: world
  
this is the yield 3
  

  这边可以清楚的看出send之后的继续执行到第二个yield处,之前的代码照常执行。
  我们再对例子进行适当的修改:
  
function printer() {
  $i = 1;
  while(true) {
  echo 'this is the yield ' . (yield $i) . "\n";
  $i++;
  }
  
}
  

  
$printer = printer();
  
var_dump($printer->send('first'));
  
var_dump($printer->send('second'));
  执行一下会发现结果为:
  

this is the yield first  
int(2)
  
this is the yield second
  
int(3)
  

  让我们来看一下,是不是发现了问题,跑出来的结果不是从1开始的而是从2开始,这是为啥嘞,我们来分析一下:
  在此之前我们先来跑另外一段代码:
  
function printer() {
  $i = 1;
  while(true) {
  echo 'this is the yield ' . (yield $i) . "\n";
  $i++;
  }
  
}
  

  
$printer = printer();
  
var_dump($printer->current());
  
var_dump($printer->send('first'));
  
var_dump($printer->send('second'));
  这个时候我们会发现执行的结果变成了:
  

int(1)  
this is the yield first
  
int(2)
  
this is the yield second
  
int(3)
  

  可以看到在第一次调用生成器函数的时候,生成器已经执行到了第一个yield表达式处,所以在$printer->send('first')之前,生成器便已经yield 1出来了,只是没有对这个生成的值进行接收处理,在send()了之后,echo语句便会紧接着完整的执行,执行完毕继续执行$i++,下次循环便是var_dump(2)。
  至此,我们看到了yield不仅能够返回数据而且还可以接收数据,而且两者可以同时进行,此时yield便成了数据双向传输的工具,这就为了实现协程提供了可能性。
  至于接下来的协程的知识,水平有限不好介绍,还是看鸟哥的原文比较直接,里面例子很丰富,介绍的很详尽。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-429363-1-1.html 上篇帖子: 【PHP系列】PHP 7.0新增特性详解 下篇帖子: php钩子程序设计
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表