php的yield是在php5.5版本就出来了,而在初级php界却很少有人提起,我就说说个人对php yield的理解
在php中,除了数组,对象可以被foreach遍历之外,还有另外一种特殊对象,也就是继承了iterator接口的对象,也可以被对象遍历,但和普通对象的遍历又有所不同,下面是3种类型的遍历情况:
可以看出,迭代器的遍历,会依次调用重置,检查当前数据,返回当前指针数据,指针下移方法,结束遍历的条件在于检查数据返回true或者false
生成器
生成器和迭代器类似,但也完全不同
生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。
生成器使用yield关键字进行生成迭代的值
例如:
一:生成器方法
生成器它的内部实现了以下方法:
Generator implements Iterator {
二:语法
生成器的语法有很多种用法,需要一一说明,首先,yield必须有函数包裹,包裹yield的函数称为"生成器函数",该函数将返回一个可遍历的对象
1:颠覆常识的yield
可能你在这发现了几个东西,和之前php完全不同的认知,如果你没发现,额,那我提出来吧
1:在调用函数返回的时候,可以发现for里面的语句并没有执行
2:在遍历一次的时候,可以发现调用函数,却没有正常的for循环3次,只循环了一次
3:在遍历一次的情况时,"存在感2"竟然没有调用,在一直遍历的情况下才调用
再看看另一个例子:
什么????while(ture)竟然还能正常的执行下去???没错,生成器函数就是这样的,根据这个例子,我们发现了这些东西:
1:while(true)没有阻塞调用函数下面的代码执行,却导致了下面的echo "额额额"和return 无法执行
2:return 返回值竟然是没有作用的
3:send(1)时,没有echo "哈哈",send(2)时,才开始出现"哈哈",
2:yield的其他语法
yield表达式中,也可以赋值,但赋值需要使用括号包裹:
只需要在表达式后面加上$key=>$value,即可生成键值的数据:
在函数前增加引用定义,就可以像returning references from functions(从函数返回一个引用)一样 引用生成值
三:特性总结
1:yield是生成器所需要的关键字,必须在函数内部,有yield的函数叫做"生成器函数"
2:调用生成器函数时,函数将返回一个继承了Iterator的生成器
3:yield作为表达式使用时,可将一个值加入到生成器中进行遍历,遍历完会中断下面的语句运行,并且保存状态,当下次遍历时会继续执行(这就是while(true)没有造成阻塞的原因)
4:当send传入参数时,yield可作为一个变量使用,这个变量等于传入的参数
协程
一:实现个简单的协程
协程,是一种编程逻辑的转变,使多个任务能交替运行,而不是之前的一直根据流程往下走,举个例子
当有一个逻辑,每次调用这个文件时,该文件要做3件事:
1:写入300个文件
2:发送邮件给500个会员
3:插入100条数据
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<?php
function task1(){
for ( $i =0; $i <=300; $i ++){
usleep(3000);
echo "写入文件{$i}\n" ;
}
}
function task2(){
for ( $i =0; $i <=500; $i ++){
usleep(3000);
echo "发送邮件{$i}\n" ;
}
}
function task3(){
for ( $i =0; $i <=100; $i ++){
usleep(3000);
echo "插入数据{$i}\n" ;
}
}
task1();
task2();
task3();
|
这样,就实现了这3个功能了,然而,技术组长又说:
能不能改成交替运行呢?
就是说,写入文件一次之后,马上去发送一次邮件,然后再去插入一条数据
然后我改一改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
<?php
function task1( $i )
{
if ( $i > 300) {
return false;
}
echo "写入文件{$i}\n" ;
usleep(3000);
return true;
}
function task2( $i )
{
if ( $i > 500) {
return false;
}
echo "发送邮件{$i}\n" ;
usleep(3000);
return true;
}
function task3( $i )
{
if ( $i > 100) {
return false;
}
echo "插入数据{$i}\n" ;
usleep(3000);
return true;
}
$i = 0;
$task1Result = true;
$task2Result = true;
$task3Result = true;
while (true) {
$task1Result && $task1Result = task1( $i );
$task2Result && $task2Result = task2( $i );
$task3Result && $task3Result = task3( $i );
if ( $task1Result ===false&& $task2Result ===false&& $task3Result ===false){
break ;
}
$i ++;
}
|
运行一下:
代码1:
代码2:
确实是实现了任务交替执行,但是代码2明显让代码变的非常的难读,扩展性也很差,那么,有没有更好的方式实现这个功能呢?
这时候我们就必须借助yield了
首先,我们得封装一个任务类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
class Task {
protected $taskId ;
protected $coroutine ;
protected $sendValue = null;
protected $beforeFirstYield = true;
public function __construct( $taskId , Generator $coroutine ) {
$this ->taskId = $taskId ;
$this ->coroutine = $coroutine ;
}
public function getTaskId() {
return $this ->taskId;
}
public function setSendValue( $sendValue ) {
$this ->sendValue = $sendValue ;
}
public function run() {
if ( $this ->beforeFirstYield) {
$this ->beforeFirstYield = false;
var_dump( $this ->coroutine->current());
return $this ->coroutine->current();
} else {
$retval = $this ->coroutine->send( $this ->sendValue);
$this ->sendValue = null;
return $retval ;
}
}
public function isFinished() {
return ! $this ->coroutine->valid();
}
}
|
这个封装类,可以更好的去调用运行生成器函数,但只有这个也是不够的,我们还需要一个调度任务类,来代替前面的while:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
class Scheduler {
protected $maxTaskId = 0;
protected $taskMap = [];
protected $taskQueue ;
public function __construct() {
$this ->taskQueue = new SplQueue();
}
public function newTask(Generator $coroutine ) {
$tid = ++ $this ->maxTaskId;
$task = new Task( $tid , $coroutine );
$this ->taskMap[ $tid ] = $task ;
$this ->schedule( $task );
return $tid ;
}
public function schedule(Task $task ) {
$this ->taskQueue->enqueue( $task );
}
public function run() {
while (! $this ->taskQueue->isEmpty()) {
$task = $this ->taskQueue->dequeue();
$task ->run();
if ( $task ->isFinished()) {
unset( $this ->taskMap[ $task ->getTaskId()]);
} else {
$this ->schedule( $task );
}
}
}
}
|
很好,我们已经有了一个调度类,还有了一个任务类,可以继续实现上面的功能了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
function task1()
{
for ( $i = 0; $i <= 300; $i ++) {
usleep(3000);
echo "写入文件{$i}\n" ;
yield $i ;
}
}
function task2()
{
for ( $i = 0; $i <= 500; $i ++) {
usleep(3000);
echo "发送邮件{$i}\n" ;
yield $i ;
}
}
function task3()
{
for ( $i = 0; $i <= 100; $i ++) {
usleep(3000);
echo "插入数据{$i}\n" ;
yield $i ;
}
}
$scheduler = new Scheduler;
$scheduler ->newTask(task1());
$scheduler ->newTask(task2());
$scheduler ->newTask(task3());
$scheduler ->run();
|
除了上面的2个类,task函数和代码1不同的地方,就是多了个yield,那我们试着运行一下:
很好,我们已经实现了可以调度任务,进行任务交叉运行的功能了,这就是"协程"
协程可以将多个不同的任务交叉运行
二:协程与调度器的通信
我们在上面已经实现了一个协程封装了,但是任务和调度器缺少了通信,我们可以重新封装下,使协程当中能够获取当前的任务id,新增任务,以及杀死任务
先封装一下调用的封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class YieldCall
{
protected $callback ;
public function __construct(callable $callback )
{
$this ->callback = $callback ;
}
public function __invoke(Task $task , Scheduler $scheduler )
{
$callback = $this ->callback;
return $callback ( $task , $scheduler );
}
}
|
同时我们需要小小的改动下调度器的run方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public function run()
{
while (! $this ->taskQueue->isEmpty()) {
$task = $this ->taskQueue->dequeue();
$retval = $task ->run();
if ( $retval instanceof YieldCall) {
$retval ( $task , $this );
continue ;
}
if ( $task ->isFinished()) {
unset( $this ->taskMap[ $task ->getTaskId()]);
} else {
$this ->schedule( $task );
}
}
}
|
新增 getTaskId函数去返回task_id:
1
2
3
4
5
6
7
8
9
10
11
|
function getTaskId()
{
return new YieldCall(
function (Task $task , Scheduler $scheduler ) {
$task ->setSendValue( $task ->getTaskId());
$scheduler ->schedule( $task );
}
);
}
|
然后,我们再修改下task1,task2,task3函数:
-
librespeed/speedtest: Self-hosted Speedtest for HTML5 and more. Easy setup, exam
阅读:1298|2022-08-30
-
avehtari/BDA_m_demos: Bayesian Data Analysis demos for Matlab/Octave
阅读:1208|2022-08-17
-
女人怀孕后,为了有一个健康聪明的宝宝,经历各种体检、筛查。其实这些体检和筛查中的
阅读:1008|2022-11-06
-
A vulnerability was found in Itech Movie Portal Script 7.36. It has been declare
阅读:869|2022-07-29
-
medfreeman/markdown-it-toc-and-anchor: markdown-it plugin to add a toc and ancho
阅读:1411|2022-08-18
-
万里平台石家庄会场,领略大美石家庄,用诗歌赞美我可爱的家乡石家庄。 我可爱的家乡
阅读:524|2022-07-30
-
sydney0zq/covid-19-detection: The implementation of A Weakly-supervised Framewor
阅读:519|2022-08-16
-
Matlab,批量,绘图,文件读取
阅读:537|2022-07-18
-
离中国最远的国家是阿根廷。从太平洋直线计算,即往东线走,北京到阿根廷的布宜诺斯艾
阅读:670|2022-11-06
-
INI文件操作 (1)INI文件的结构: ;这是关于INI文件的注释部分 关键字=值 ...
阅读:585|2022-07-18
|
请发表评论