百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>网页制作> 理解PHP yield的高级用途
分享文章到:

理解PHP yield的高级用途

发布时间:09/01 来源:未知 浏览: 关键词:

yield 语法参加 PHP

yield语法是在版本5.5参加PHP的,配合迭代器使用,功效上就是 流程操纵 代码,和gotoreturn 相似。

以下就是官方供给的 yield 小例子,通过施行结果,我们可剖析今世码施行到 yield $i 时,他会停止 return $i, 待 echo "$value\n" 后, goto for ($i = 1; $i <= 3; $i++) {, 对!PHP 的 yield 就是一个能出能进的语法。在z代码中七进七出,把 $i 平安然安得送了出来。

<?phpfunction gen_one_to_three() {
    for ($i = 1; $i <= 7; $i++) {
        //留意变量$i的值在不一样的yield之间是保持传递的。
        yield $i;
    }}$generator = gen_one_to_three();foreach ($generator as $value) {
    echo "$value\n";}// output12...67

我们碰到了什么问题

写代码就是解决问题。我们来看看他们碰到了什么问题:php官方呢,需要三言两语地把yield介绍给大家。一部分网友呢,需要在有限的资源内完成大文件操纵。而我们的鸟哥。面临的一群对当下yield的教程逗留于初级而不中意的phper,就以一个任务调度器作为例子,给大家讲了一种yield高级用途。

php.net:生成器语法,
PHP怎样读取大文件,
风雪之隅:在PHP中使用协程实现多任务调度.

提出问题,再用yield来解答,看到以上答案,我觉得呢,这PHP协程不外如此(Go协程比拟 )。

有句话——一个好问题比答案更重要,当前广阔网友还没有给yield提出更好,更艰难的问题。

yield这个进进出出的语法,许多举例都是再让yield做迭代器啊,或者利用低内存读取超大文本的Excelcsv什么的,再高级就是用它实现一个简便的任务调度器,并且这个调度器,一看代码都差不多。

我来出道题

正如一个好的问题,比答案更有价值

  1. 用PHP实现一个 Socket Server,他能接收恳求,并返回Server的时间。

好,这是第一个问题,铺垫。 官方答案

  1. 在本来的代码上,我们加个需求,该Socket Server 处置恳求时,依靠其他 Socket Server,还需要有 Client 功效。也就是他能接收恳求,向其它Server发起恳求。

这是第二个问题,也是铺垫。

  1. 本来的Socket Server统一时间只能效劳一个客户,但愿能实现一个 非堵塞I/O Socket Server, 这个 Server 内有 Socket Client 功效,支撑并发处置收到的恳求,和主动发起的恳求。要求不消多线程,多进程。

这个问题,还是铺垫,这几个问题很干,大家可以想一想,2,3题的答案,都放在一个足本里了:nio_server.php

以上这段代码,我列举了一个详细的业务,就是会员恳求购物车加购动作, 而购物车效劳呢,又需要和 产品效劳,库存效劳,优待效劳 交互,来验证加购动作可行性。有同步,异步方式恳求,并做对照。

后续还有许多代码,我都放gitee链接了。使用办法,见readme.md

  1. 最后一个问题:在PHP中,用同步写代码,程序呢异步施行?需要如何调整代码。

提醒:这个和 PHPyield 语法有关。

再提醒:yield 语法特点是啥,进进出出!

看着我们的代码,同步, 异步,进进出出 你想到了什么?

看到代码,同步处置模式下,这三个函数checkInventory checkProduct checkPromo 时,发起恳求,并顺次等候返回的结果,这三个函数施行后,再响应客户恳求。

异步处置模式下,这三个函数发起恳求完毕后,代码就跳出轮回了,然后是在select()下的一个代码分支中接收恳求, 并收集结果。每次收到结果后推断可否完成,完成则响应客户端。

那么能不克不及这样:在异步处置的流程中,当 Server收到 本人发起的 client 有数据响应后,代码跳到 nio_server.php 的 247行呢,这样我们的收到恳求校验相关的代码就能放到这里,编码能就是同步,容易懂得。不然,client 的响应处置放在 280 行今后,不通过抓包,真的很难懂得,施行了第 247 行代码后,紧接着是从 280 行开端的。

诶~这里是不是有 进进出出 那种感受了~ 代码从 247 行出去,开端监听发出 Client 响应,收到返回数据,带着数据再回到 247 行,连续停止逻辑校验,综合结果后,再响应给客户端。

用yield来解决问题

基于 yield 实现的,同步编码,"异步"I/OSocket Server 就实现了。代码。

这里 “异步” 打了引号,大佬别扣这个字眼了。 该是非堵塞I/O

不等大家的答案了,先上我的结果代码吧,代码呢都放在这个名目下了。

gitee https://gitee.com/xupaul/PHP-generator-yield-Demo/tree/master/yield-socket

运转测试代码

clone 代码到当地后,需要拉起4个 command 命令程序:

拉起3个第三方效劳

## 启动一个处置耗时2s的库存效劳$ php ./other_server.php 8081 inventory 2## 启动一个处置耗时4s的产品效劳$ php ./other_server.php 8082 product 4## 监听8083端口,处置一个恳求 耗时6s的 promo 效劳$ php ./other_server.php 8083 promo 6

启动购物车效劳

## 启动一个非堵塞购物车效劳$ php ./async_cart_server.php 

## 或者启动一个一样购物车效劳$ php ./cart_server.php

发起会员恳求

$ php ./user_client.php

运转结果呢如下,通过施行的时间日志,可得这三个恳求是并发发起的,不是堵塞通讯。

在看我们的代码,三个函数,发起socket恳求,没有设定callback,而是通过yield from 接收了三个socket的返回结果。

也就是到达了,同步编码,异步施行的结果。

运转结果

非堵塞模式

client 端日志:

通过以上 起始时间完毕时间 ,就看到这三个恳求耗时总共就6s,也就依照耗时最长的promo效劳的耗时来的。也就是说三个第三方恳求都是并发停止的。

cart server 端日志:

而 cart 打印的日志,可以看到三个恳求一并发起,并一起等候结果返回。到达非堵塞并发恳求的结果。

堵塞模式

client 端日志:

以上是堵塞方式恳求,可以看到耗时 12s。也就是三个效劳加起来的耗时。

cart server 端日志:

cart 效劳,顺次堵塞方式恳求第三方效劳,次序施行完毕后,共耗时12s,当然假如第一个,获第二个效劳报错的话,会提早完毕这个检查。会节省一点时间。

工作道理

这里就是用到了 yield 的工作特点——进进出出,在发起非堵塞socket恳求后,不是堵塞方式等候socket响应,而是使用yield跳出当前施行生成器,等候有socket响应后,在调取生成器的send办法回到发起socket恳求的函数内,在 yield from Async::all() 接收数据响应数据搜集完毕后,返回。

和Golang比一比

思考到网速缘由,我这就放上一个国内教程链接:Go 并发 教程

php的协程是真协程,而Go是披着协程外衣的轻量化线程(“协程”里,都玩上“锁”了,这就是线程)。

我个人偏爱,协程的,觉得线程的调度有必然随机性,因此需要锁机制来包管程序的准确,带来了额外开销。协程的调度(换入换出)交给了会员,包管了一段代码施行持续性(当然进程级上,还是会有换入换出的,除非是跨进程的资源拜访,或者跨机器的资源拜访,这时,就要用到分布式锁了,这里不展开计议),同步编码,异步施行,只需要思考阿谁哪个办法会有IO交互会协程跳出即可。

和NodeJS比划一下

Javascript 和 PHP 两个足本说话有许多类似的地方,弱类型,动态对象,单线程,在Web领域生态丰硕。不一样的是,Javascript在阅读器端一开端就是异步的(假如js发起网络恳求只能同步停止,那么你的网页渲染线程会卡住),例如AjaxsetTimeoutsetInterval,这些都是异步+回调的方式工作。

基于V8引擎而产生的NodeJS,天生就是异步的,在供给高机能网络效劳有很大的优势,不外它的IO编码范式么。。。刚开端是 回调——毁掉地狱,后来有了Promise——屏幕竖起来看,乃至Generator——遇事不停yield一下吧,到此刻的Async/Await——语法糖?真香!

可以说JS的委员非常勤快,在异步编程范式的标准拟定也做的很好(之前我尝试写NodeJS时,几个回调就直接把我劝退了),2009年产生的NodeJS有点青出于蓝的意思。当前PHP只是赶上了协程,等待PHP的Async/Await语法糖的实现吧。

PHP yield 使用留意事项

一旦使用上 yield 后,就必需留意调取函数是,会得到函数结果,还是 生成器对象。PHP 不会主动帮你不同,需要你手动代码推断结果类型—— if ($re instanceof \Generator) {}, 假如你得到的是 生成器,但不但愿去手动调取 current() 去施行它,那么在生成器前 使用 yield from 交给上游(框架)来解决。

爆改 Workerman

博客写到这,就开端手痒痒了,看到Workerman框架,我在根基上二开,使其能——同步编码,异步施行

代码已放到:PaulXu-cn/CoWorkerman.git

当前还是dev阶段,大家喜爱可以先 体验一波。

$ composer require paulxu-cn/co-workerman

一个简便的单线程 TCP Server

<?php// file: ./examples/example2/coWorkermanServer.php , 具体代码见github$worker = new CoWorker('tcp://0.0.0.0:8080');// 设定fork一个子进程$worker->count = 1;$worker->onConnect = function (CoTcpConnection  $connection) {
    try {
        $conName = "{$connection->getRemoteIp()}:{$connection->getRemotePort()}";
        echo PHP_EOL . "New Connection, {$conName} \n";

        $re = yield from $connection->readAsync(1024);
        CoWorker::safeEcho('get request msg :' . $re . PHP_EOL );

        yield from CoTimer::sleepAsync(1000 * 2);

        $connection->send(json_encode(array('productId' => 12, 're' =>true)));

        CoWorker::safeEcho('Response to :' . $conName . PHP_EOL . PHP_EOL);
    } catch (ConnectionCloseException $e) {
        CoWorker::safeEcho('Connection closed, ' . $e->getMessage() . PHP_EOL);
    }};CoWorker::runAll();

这里设定fork 一个worker线程,处置逻辑中带有一个sleep() 2s的操纵,仍然不影响他同时响应多个恳求。

启动测试程序

## 启动CoWorker效劳$ php ./examples/example2/coWorkermanServer.php start## 启动恳求线程$ php ./examples/example2/userClientFork.php

运转结果

绿色箭头——新的恳求,红色箭头——响应恳求

从结果上看到,这一个worker线程,在接收新的恳求同时,还在回复此前的恳求,各个连接交织运转。而我们的代码呢,看模样就是同步的,没有回调。

CoWorker购物车效劳

好的,这里我们做几个简便的微效劳模拟实际利用,这里模拟 会员恳求端购物车效劳库存效劳产品效劳。 模拟会员恳求加购动作,购物车去离别恳求 库存,产品 校验会员可否可以加购,并响应客户恳求可否成功。

代码我就不贴了,太长了,费事移步 CoWorkerman/example/example5/coCartServer.php

运转命令

## 启动库存效劳$ php ./examples/example5/otherServerFork.php 8081 inventory 1## 启动产品效劳$ php ./examples/example5/otherServerFork.php  8082 product 2
## 启动CoWorker 购物车效劳$ php ./examples/example5/coCartServer.php start
## 会员恳求端$ php ./examples/example5/userClientFork.php

运转结果

黄色箭头——新的会员恳求,蓝色箭头——购物车发起库存,产品检查恳求,红色箭头——响利用户恳求

从图中看到也是用1个线程效劳多个连接,交织运转。

好的,那么PHP CoWorkerman 也能像 NodeJS 那样用 Async/Await 那样同步编码,异步运转了。

快来试试这个 CoWorkerman 吧:

$ composer require paulxu-cn/co-workerman

工作道理

先上图:

图的上部是Workerman 的工作泳道图,图下部是CoWorkerman的工作泳道图。

workerman内的worker进程碰到堵塞函数的处置方式时,会等候IO返回,假如这个时候,又有了新的恳求,那么闲的worker会竞争到这个新的连接。

我在上图worker5中,描写了一个AsyncTCPConnection使用状况,woker内发起了一个非堵塞恳求,并注册了回调函数,然后程序连续运转到完毕。当异步恳求响应时,就需要通过其他方式去响应(如本人再发起一个恳求告知恳求方)。

鄙人图中CoWorkerman,也是多个Worker竞争新的恳求,当worker1收到一个新的恳求,会发生一个生成器,生成器内发起异步恳求,并注册响应回调,恳求响应后,回到该生成器跳出(yield)的地方,连续施行代码。

发起异步恳求,并注册回调函数,这些默许工作CoWorkerman框架内已做了,回调函数内工作是:收到数据,并发给 发起该恳求的生成器。

这例子中,通过调取 Promise:all() 发起多个恳求,并监听结果返回,待所有的响应返回再连续运转生成器

在程序yield跳出后,该worker就处于事件轮回状态($event->loop()),也就是多路监听:恳求端口,第三方客户端恳求响应端口。这个时候假如:

  1. 有新的恳求来,他和其他 worker 竞争新的恳求,假如竞争到了,则该worker内又发生一个新的 生成器。
  2. 客户端有响应,则调取回调函数
  3. 客户端都响应了,连续运转 生成器程序。

从1中,我们可假设,假如就一个 Worker,那么该 Worker 可以在上一个恳求未完成状况下,连续接受处置下一个恳求。也就是 CoWorkerman 可以在单 Worker 下运转,并发处置多个恳求。

当然,这里也有个前提,单 Worker 模式内不克不及运转堵塞函数,一旦堵塞,后续恳求就会堵在网卡。所以,除非对本人的代码非常理解,假如用到第三方库,那么我还是倡议你在多 Worker 模式下运转 CoWorkerman,堵塞时,还有其他Worker兜住新恳求。

CoWorkerman 的意义

  1. 用同步的代码,发起异步恳求,多个恳求可并发,从IO串行等候,改为并行等候,减少无畏的等候时间。提高业务程序的效力同时,不落低代码可读性。
  2. 在一个线程内通过事件轮回,尽大概处置多个恳求,缓解了一个恳求一个线程带来的频繁线程切换,从中心上提高运转效力。

CoWorkerman 生态位

适合处置纯Socket恳求的利用,如Workerman Gateway,或者是 大前端 整合多个效劳RPC结果, 综合后返给前三页这样的场景.

日志记载是每个程序最根本需求,由于写文件函数是堵塞的,倡议用新闻队列,或者redis队列,更或者跳过Logstash直接丢Elasticsearch.

CoWorkerman有他的局限性,也有他本人位置。

总结

好~PHP 协程编码到 网络异步编码就到此完毕了,假如看到本文章有许多迷惑,欢迎留言发问,假如是 yield 语法不太记得,可以先读一读这个系列前几篇文章复习一下。

假如行,请三连。CoWorkerman 感谢!

懂王

以上就是理解PHP yield的高级用途的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有151人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板