php协程处理报表,php 协程 yield

什么是协程

理解协程之前最好要理解进程和线程,这里不过多解释,简单来说,进程是资源分配的最小单位,线程是进程中一个单一的执行流,线程共享进程资源,每个线程都有自己独立的栈空间。线程相对于进程而言更加轻量,操作系统调度进程切换的代价很大,需要保存当前进程的各种信息,PCB 进程控制块。线程切换相对更加容易,线程同属于一个进程,只需要切换栈空间。多线程更能利用多核的 cpu,发挥性能。

协程呢,可以说是断点,就如同程序调试时的断点一样,协程可以打破程序顺序执行的特征,协程可以帮助我们调整代码的执行顺序,来实现任务协同工作。网上有好多协程与线程的对比文章,有的说协程比线程好,有的则说线程好,我也尝试了 php 中的协程,使用 yield 关键字。通过我的尝试以及我的理解,我认为二者各自优点,但是我还是更加倾向于线程。我觉得线程时任务并行成为可能,而协程只是改变了代码的执行顺序,能用协程做到的,我们可以通过调整代码的顺序来实现相同的效果。

还有一点就是,有人说协程可以实现异步,我却没有发现协程这方面的作用,我觉得可以用协程调度程序任务,然后用线程并行来实现异步,但是目前我还不会

php 中协程的使用

php 中的协程就是在函数中使用 yield 关键字,就可以成为一个生成器来实现协程,生成器是 Geneator 的一个实例对象,所以最好先理解一下迭代器提供的几个抽象函数,rewind(), current(), next(), valid()比如下面一个简单的例子

function go(){

yield;

echo 'yield ';

yield;

echo 'end';

};

$test = go();

假如函数中没有那两个 yield 关键字,上述代码会输出 yield end,可是加上 yield,会发现程序什么输出都没有,这是因为 yield 相当于一个断点,函数走到这里会停止,让出 cpu ,这也是协程的工作原理

包含 yield 的函数是生成器函数,不同于普通函数,生成器函数被调用时,实际是返回了一个可迭代的对象,是 Geneator类 的一个实例,迭代对象通过 next() 方法可以遍历到下一个对象,对于生成器,可以使用 next() 实现程序的继续执行。

还是上面的例子,详细解释一下如何工作,加深自己的理解

$yield= function(){

yield;

echo 'yield ';

yield;

echo 'end';

};

$test = $yield();// 生成一个Geneator 的实例对象,对象实例化的时候,会执行一次 rewind() 相当于生成器函数执行到第一次 yield 处中断函数

$test->next();// 输出 yield 调用 next() 就是使程序继续执行,执行到下一个 yield 处再次中断,所以会输入 yield

$test->next();// 输出 end 同上,这个执行后,函数内部不再有中断点

var_dump($test->valid()); // 这里会输出 false 说明迭代结束

下面再介绍一个函数 send(),可以实现我们与生成器的通信。先说一下总结的结论,send() 与 next() 都能让生成器向后迭代一步,在函数中表现出来就是让函数再向下执行,直到遇到下一个 yield。next() 比较直接,就是调度生成器执行至下一个断点处,没有返回值,send() 相对而言增加了通信效果,send() 发送数据至生成器,被 yield 关键字接收,可以赋值给变量,同时执行至下一处断点 yield 处,将 yield 右边的数据作为返回值返回。

相比来说,send()相当于执行 next() 后执行 current();next() 相当于 send(null),而且不取返回值;虽然这样表达不是很准确

还是写几个例子便于理解掌握

// 上面的例子中,只单独使用了 yield 关键字,其实 yield 左右两边都是可以跟表达式和变量的

// 先来一个简单的便于理解

function work(){

$str = yield 'first yield';

echo $str;

yield 'end';

}

$s = work();

var_dump($a->current()); // string(11) "first yield"

var_dump($s->next()); // NULL

// 如果将上面的 var_dump($s->next()) 换成 $s->send('php') 会输出 php 并得到 string(3) "end"

迭代器的 current() 以及 send() 会返回当前 yield 右侧的值,如果右侧没有,则返回空,next() 则是继续上一个断点,继续执行到下一个 yield 或者迭代器结束的地方,比如 return 语句,生成器的迭代是通过 valid() 来判断的,valid() 返回 false 时说明迭代结束

$work = function(){

$i = 0;

while(++$i < 10){

$str = yield $i;

var_dump($str);

}

};

// case 1

$test = $work();

foreach($test as $v){

echo $v,PHP_EOL;

}

// 上面的foreach 实际上就是

foreach($test->rewind(),$v = $test->current();$test->valid();$test->next(),$v = $test->current()){

echo $v,PHP_EOL;

}

// case 2

$i = 0;

$test = $work();

while($test->valid()){

$test->send('send '.$i++);

}

// 上述两段代码输出不同 能看出 next() 就是仅仅向下执行,不传入数据

// send() 会传入数据,在生成器中被 yield 接收,用来传递给左侧变量(没有赋值操作的话的话 send() 也就没有使用的意义)

一个更能看出的例子

$s = (function($num){

while(--$num){

var_dump(yield);

}

})(10);

$i = 0;

while($s->valid()){

//$s->next();

$s->send(++$i);// 试着将这条语句注释,执行上方的语句,看看有什么不同的效果

}

除了可以设置生成器每次迭代的值之外,也可以设置键

function work($i){

while(--$i > 0){

yield $i * $i => $i;

}

}

$test = work(5);

while($test->valid()){

echo $test->key(),'----',$test->current(),PHP_EOL;

$test->next();

}

如果不使用 $key => $value 的格式,右侧的变量只能作为 value,键值默认为数字索引

使用协程写两个例子

生产者与消费者 参考链接

const NUM = 10; // 设置生产个数,作为终止条件

$consumer = (function(){

while(true){

$receive = yield; // 设置断点

echo 'receive '.$receive,PHP_EOL;

echo 'consume end',PHP_EOL;

}

})();

$producer = (function($num) use ($consumer){

echo 'start produce',PHP_EOL,'-----',PHP_EOL;

while(--$num > 0){

echo 'produce num '.$num,PHP_EOL;

$consumer->send('produce num '.$num); // 生产后通知 consumer 消费

}

echo '------',PHP_EOL,'produce end';

})(NUM);

再写一个之前线程实现的 线程同步打印 ABC

/**

* 实现方式,A 打印后通知 B ,B 打印后通知 C,C 打印后返回 A 处,继续执行

* 所以将 B 和 C 用生成器实现,A 调用 B,B 调用 C

*/

const NUM = 10; // 打印次数

const SLEEP_TIME = 1e6; //休眠时间

$c = (function(){

while(true){

yield;

echo 'C';

usleep(SLEEP_TIME);// 为了看出效果,延时一下

}

})();

$b = (function() use ($c){

while(true){

yield;

echo 'B';

usleep(SLEEP_TIME);

$c->next();

}

})();

$a = (function($num) use ($b){

while (--$num > 0) {

echo 'A';

usleep(SLEEP_TIME);

$b->next();

}

echo PHP_EOL,'print end';

})(NUM);

个人见解

通过对协程的使用,我还是认为协程只是调整了代码的执行顺序,(能通过协程实现的,我们可以自己改写代码顺序来实现),并不能实现并行。协程切换的代价小,但是依旧不能取代线程的作用,协程如果能配合线程,来实现异步,将任务分发给线程,通过协程调度任务,应该会是一个不错的方案

标签:协程,send,next,线程,yield,test,php

来源: https://www.cnblogs.com/mlover/p/11134493.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值