虽然之前就接触了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便成了数据双向传输的工具,这就为了实现协程提供了可能性。

至于接下来的协程的知识,水平有限不好介绍,还是看鸟哥的原文比较直接,里面例子很丰富,介绍的很详尽。

转: https://www.cnblogs.com/tingyugetc/p/6347286.html

参考: https://www.cnblogs.com/lynxcat/p/7954456.html

PHP的生成器、yield和协程的更多相关文章

  1. Python并发实践_02_通过yield实现协程

    python中实现并发的方式有很多种,通过多进程并发可以真正利用多核资源,而多线程并发则实现了进程内资源的共享,然而Python中由于GIL的存在,多线程是没有办法真正实现多核资源的. 对于计算密集型 ...

  2. Python并发编程之从生成器使用入门协程(七)

    大家好,并发编程 进入第七篇. 从今天开始,我们将开始进入Python的难点,那就是协程. 为了写明白协程的知识点,我查阅了网上的很多相关资料.发现很难有一个讲得系统,讲得全面的文章,导致我们在学习的 ...

  3. python 列表表达式、生成器表达式和协程函数

    列表表达式.生成器表达式和协程函数 一.列表表达式: 常规方式示例: egg_list=[] for i in range(100): egg_list.append("egg%s" ...

  4. 深入理解yield(二):yield与协程

    转自:http://blog.beginman.cn/blog/133/ 协程概念 1.并发编程的种类:多进程,多线程,异步,协程 2.进程,线程,协程的概念区别: 进程.线程和协程的理解 进程:拥有 ...

  5. 用yield 实现协程 (包子模型)

    协程是一种轻量级的线程 无需线程上下级的开销, 所有的协程都在一个线程内执行 import time def consumer(name): print('%s is start to eat bao ...

  6. python yield实现协程(生产者-消费者)

    def customer(): r="" while True: n=yield r#,接收生产者的消息,并向消费者发送r print("customer receive ...

  7. php 通过 yield 实现协程有什么使用场景

    来源:https://segmentfault.com/q/1010000010018151 参考:https://www.cnblogs.com/lynxcat/p/7954456.html 协程可 ...

  8. 用yield写协程实现生产者消费者

    思路: yield可以使得函数阻塞,next,和send可以解阻塞,实现数据不竞争的生产者消费者模式 代码: import random #随机数,模拟生产者的制造物 def eat(): #消费者 ...

  9. python基于yield实现协程

    def f1(): print(11) yield print(22) yield print(33) def f2(): print(55) yield print(66) yield print( ...

随机推荐

  1. 让网络编程更轻松和有趣 t-io

    原文:https://www.oschina.net/p/t-io 注意:还是尽量去看原文,因为原文下面的评论也很有意思,可以参考大牛的讨论学习到新的东西 授权协议:Apache 开发语言:Java ...

  2. VTK三维点集轮廓凸包提取

    碰撞检测问题在虚拟现实.计算机辅助设计与制造.游戏及机器人等领域有着广泛的应用,甚至成为关键技术.而包围盒算法是进行碰撞干涉初步检测的重要方法之一.包围盒算法是一种求解离散点集最优包围空间的方法.基本 ...

  3. 7、java5线程池之单一线程池newSingleThreadExecutor

    JDK文档说明: 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程.(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执 ...

  4. python必须要安装的库

    1.requests 2.lxml 3.Django 4.BeautifulSoup 5.PyMySQL-0.7.0 (适用于python3) 6.图片处理PIL

  5. php Socket表单提交学习一下

    <?php //发送请求指定的页面 $file = "1.php"; $filename = "gitignore.txt"; //文件名 $path = ...

  6. Mysql中字符串正确的连接方法

    虽然SQL server和My sql的语句基本都一致,但是仍然存在一些小区别.就如字符串的连接来说,SQL server中的字符串连接是使用“+”来连接,不带引号sql server是做加法运算.而 ...

  7. 【php+微擎】微擎学习相关帮助推荐

    我刚接触微擎没多久,学习中遇到很多问题,走过很多弯路.遇到很多问题在网上也查不到,因此想把我自己的学习历程写出来供新手们参考(本人还是菜鸟一枚,文中错误之处,敬请指正!) 我以一个新手小白的角度来帮助 ...

  8. xml中“ < > ”转义为“ &lt; &gt; ”问题处理

    曾经也碰到过类似问题,解决方法是在发送或者解析报文前执行上面的方法将内容转义一下,现在我用dom4j组装如下的报文(报文体中内容传输时加密处理),大致介绍一下上面方法的使用,具体看代码. import ...

  9. Comparable 与 Comparator

    Comparable & Comparator 都是用来实现集合中元素的比较.排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序 ...

  10. memcached完全剖析--1. memcached的基础

    翻译一篇技术评论社的文章,是讲memcached的连载.fcicq同学说这个东西很有用,希望大家喜欢. 发表日:2008/7/2 作者:长野雅广(Masahiro Nagano) 原文链接:http: ...