Skip to content

Commit cb16832

Browse files
author
yuan.qianpeng
committed
php yield(上)
1 parent 1e24b89 commit cb16832

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

17. PHP中的yield(上).md

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
其实,我并不是因为迭代或者生成器或者研究PHP手册才认识的yield,要不是协程,我到现在也不知道PHP中还有yield这么个鬼东西。人家这个东西是从PHP 5.5就开始引入了,官方名称叫做生成器。你要说为什么5.5年代的东西,现在才拿出来。我还想问你哟,PHP 5.3就有了的namespace为毛到最近这几年才开始正式投产。
2+
3+
那么,问题来了,这东西到底是有何用?
4+
5+
先来感受一个问题,给你100Kb的内存(是的,你没有看错,就是100Kb),然后让你迭代输出一个从1开始一直到10000的数组,步进为1。
6+
7+
愈先迭代数组,必先创造数组。
8+
9+
所以,脑门一拍,代码一坨如下:
10+
11+
```php
12+
<?php
13+
$start_mem = memory_get_usage();
14+
$arr = range( 1, 10000 );
15+
foreach( $arr as $item ){
16+
//echo $item.',';
17+
}
18+
$end_mem = memory_get_usage();
19+
echo " use mem : ". ( $end_mem - $start_mem ) .'bytes'.PHP_EOL;
20+
```
21+
22+
一顿操作猛如虎,运行一下成绩1-5,你们感受一下:
23+
24+
![](http://static.ti-node.com/6426293690343358464)
25+
26+
528440bytes,约莫就是528Kb,几乎是100Kb的五倍了,妈的这日子没法过了。
27+
28+
![](http://static.ti-node.com/6426294138886422529)
29+
30+
毕竟你们也知道,最近内存价格确实贵,国家也在号召低碳节能减排,你多耗费5倍内存,就意味着多排放5倍的二氧化碳,就意味着要为多用的内存多花钱贡献给棒子... ...你想想,那可是棒子。
31+
32+
人都是被逼出来的,于是yield可以来救场了,大概代码如下,注意看操作:
33+
34+
```php
35+
<?php
36+
$start_mem = memory_get_usage();
37+
function yield_range( $start, $end ){
38+
while( $start <= $end ){
39+
$start++;
40+
yield $start;
41+
}
42+
}
43+
foreach( yield_range( 0, 9999 ) as $item ){
44+
echo $item.',';
45+
}
46+
$end_mem = memory_get_usage();
47+
echo " use mem : ". ( $end_mem - $start_mem ) .'bytes'.PHP_EOL;
48+
```
49+
50+
运行一下,你们感受一下:
51+
52+
![](http://static.ti-node.com/6426306722616311809)
53+
54+
首先,我们观察一下yield_range这个函数跟普通函数不一样的地方,就是普通函数往往都是使用return来返回结果,而这个中则是yield。其次是普通函数中return只能返回一次,这个yield能返回好多次。
55+
56+
那么,我们来分析一波儿这个神奇的yield_range函数。这个yield关键字到底返回的是什么?我们简单看一下:
57+
58+
```php
59+
<?php
60+
function yield_range( $start, $end ){
61+
while( $start <= $end ){
62+
$start++;
63+
yield $start;
64+
}
65+
}
66+
$rs = yield_range( 1, 100 );
67+
var_dump( $rs );
68+
/*
69+
object(Generator)#1 (0) {
70+
}
71+
*/
72+
```
73+
74+
yield返回的是一个叫做Generator(中文名就是生成器)的object对象,而这个生成器是实现了Iterator接口(至于Iterator接口,你们去PHP手册上搜索吧)。所以,既然实现了Iterator接口(也正是因为如此,这个东西可以使用foreach进行迭代,明白了吧?),所以可以有如下代码:
75+
76+
```php
77+
<?php
78+
function yield_range( $start, $end ){
79+
while( $start <= $end ){
80+
yield $start;
81+
$start++;
82+
}
83+
}
84+
$generator = yield_range( 1, 10 );
85+
// valid() current() next() 都是Iterator接口中的方法
86+
while( $generator->valid() ){
87+
echo $generator->current().PHP_EOL;
88+
$generator->next();
89+
}
90+
```
91+
92+
运行结果如下所示:
93+
94+
![](http://static.ti-node.com/6426627373801668608)
95+
96+
重点来了:这个yield_range函数似乎能够记住它上一次运行到哪儿了,上一次运行的结果是什么,然后紧接着在下一次运行的时候继续从上次终止的地方继续开始。这不是普通的PHP函数可以做得到的!
97+
98+
我们知道,操作系统在调度进程的时候,会触发一个叫做“进程上下文切换”的概念。比如CPU从进程A调度给进程B了,那么当再次从进程B调度给进程A的时候,当初进程A运行到哪儿了、临时的数据结果是什么都是需要被还原的,不然,一切都要从头,那就要出大问题了。而,这个yield关键字,似乎在用户态(非系统内核级)就可以实现这个概念。所以说,用yield搞迭代,怕是真的很没出息的一件事,它能做的太多。
99+
100+
紧接着,我们需要认识一个生成器对象的一个方法,叫做send,简单看下下面这坨代码:
101+
102+
```php
103+
<?php
104+
function yield_range( $start, $end ){
105+
while( $start <= $end ){
106+
$ret = yield $start;
107+
$start++;
108+
echo "yield receive : ".$ret.PHP_EOL;
109+
}
110+
}
111+
$generator = yield_range( 1, 10 );
112+
$generator->send( $generator->current() * 10 );
113+
```
114+
115+
运行结果如图所示:
116+
117+
![](http://static.ti-node.com/6426631112352595969)
118+
119+
send方法可以修改yield的返回值,但是,你也不能想当然,比如下面这坨代码,你们以为运行结果是什么样呢?
120+
121+
```php
122+
<?php
123+
function yield_range( $start, $end ){
124+
while( $start <= $end ){
125+
$ret = yield $start;
126+
$start++;
127+
echo "yield receive : ".$ret.PHP_EOL;
128+
}
129+
}
130+
$generator = yield_range( 1, 10 );
131+
foreach( $generator as $item ){
132+
$generator->send( $generator->current() * 10 );
133+
}
134+
```
135+
136+
本来以为运行结果是类似于这样的:
137+
138+
```php
139+
<?php
140+
yield receive : 10
141+
yield receive : 20
142+
yield receive : 30
143+
yield receive : 40
144+
yield receive : 50
145+
yield receive : 60
146+
yield receive : 70
147+
yield receive : 80
148+
yield receive : 90
149+
yield receive : 100
150+
```
151+
152+
然而,唯物主义告诉我们:
153+
154+
![](http://static.ti-node.com/6426632376524210177)
155+
156+
结果是打脸的,你们感受一下:
157+
158+
![](http://static.ti-node.com/6426632517612208129)
159+
160+
为什么我把php版本信息什么的打印出来呢?因为,这是个bug,这是个php的bug,至少我正在使用的PHP 7.1.17版本是有这个bug的,你不要以为这里面有什么高深莫测的技术,就是bug而已。下面是bug链接,你们可以去观摩一下:
161+
162+
https://bugs.php.net/bug.php?id=76104
163+
https://stackoverflow.com/questions/37817315/how-does-generatorsend-work
164+
165+
总结一句话,就是不要在foreach中使用生成器的send方法。
166+
167+
然而,我在国内的一些有关php yield的文章中,都没有看到有人提及这个bug,我坑我自己是淌过了,你们是没必要再淌了。

0 commit comments

Comments
 (0)