Maison  >  Article  >  développement back-end  >  Apprenez l'utilisation avancée de PHP Yield

Apprenez l'utilisation avancée de PHP Yield

coldplay.xixi
coldplay.xixiavant
2020-06-30 17:37:195294parcourir

Apprenez l'utilisation avancée de PHP Yield

Introduction

Quand je suis entré en contact pour la première fois avec PHP yield, je me suis demandé quel genre de technologie noire est yield ? Baidu :yield——Coroutine, générateur. De nombreux articles parlent de Iterator, Generater, bon sang~, ce truc est un complément aux itérateurs PHP. Tournez encore quelques pages et le voilà Go 协程. Par curiosité, je l'ai ouvert et j'ai regardé Go 协程. Il y avait des mots comme 并发, 线程, 管道通讯, wc, nb, c'est la technologie noire. Revenez lire PHP je veux. à transférer Go toutes les minutes.

Recommandations d'apprentissage associées : Programmation PHP du débutant au compétent

syntaxe de rendement ajoutée à PHP

La syntaxe yield a été ajoutée à PHP dans la version 5.5. Elle est utilisée avec les itérateurs et est fonctionnellement identique au code 流程控制, similaire à goto et return.

Ce qui suit est un petit exemple de rendement fourni par le responsable. Grâce aux résultats d'exécution, nous pouvons analyser que lorsque le code est exécuté à yield $i, il passera à return $i, et après , echo "$valuen" goto , c'est vrai ! Le rendement de PHP est une syntaxe qui peut entrer et sortir. Dans le code z, sept entrées et sept sorties, le for ($i = 1; $i est envoyé en toute sécurité. <code>$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

Quels problèmes avons-nous rencontrés

Écrire du code consiste à résoudre des problèmes. Jetons un coup d'œil aux problèmes qu'ils ont rencontrés : quant aux responsables PHP, ils doivent présenter le rendement à tout le monde de manière concise et concise. Certains internautes doivent effectuer des opérations sur des fichiers volumineux avec des ressources limitées. Et notre frère oiseau. J'ai rencontré un groupe de PHPers insatisfaits des tutoriels de rendement actuels qui étaient encore au niveau de base. J'ai pris un planificateur de tâches comme exemple et leur ai expliqué une utilisation avancée.

yieldphp.net : Syntaxe du générateur,

Comment lire des fichiers volumineux en PHP,

Wind and Snow Corner : Utiliser des coroutines pour implémenter la planification multi-tâches en PHP.

proposé Question, utilisez

pour y répondre. En voyant la réponse ci-dessus, je pense que cette coroutine PHP n'est rien de plus (

et yield par rapport à ). Go协程Il y a un dicton -

À l'heure actuelle, la majorité des internautes n'ont pas posé de questions meilleures et plus difficiles sur le rendement.

一个好问题比答案更重要

Cette syntaxe d'entrée et de sortie, de nombreux exemples consistent à utiliser le rendement comme itérateur, ou à utiliser une mémoire faible pour lire un texte très volumineux

, yield et ainsi de suite, peu importe comment avancé, il est utilisé pour implémenter un planificateur de tâches simple, et le code de ce planificateur est similaire à première vue. Excelcsv

Laissez-moi vous donner la réponse

Tout comme une bonne question, elle a plus de valeur que la réponse

Utilisez PHP Implémentez un serveur Socket capable de recevoir des requêtes et de renvoyer l'heure du serveur.
  1. D'accord, c'est la première question, qui prépare le terrain. Réponse officielle

Sur le code original, nous ajoutons une exigence Lorsque le Socket Server traite les requêtes, il dépend des autres Socket Servers et a également besoin de la fonction Client. Autrement dit, il peut recevoir des requêtes et lancer des requêtes vers d'autres serveurs.
  1. C'est la deuxième question et aussi la préfiguration.

Le serveur Socket d'origine ne peut servir qu'un seul client à la fois. Nous espérons implémenter un
    serveur Socket. Ce serveur a la fonction Socket Client et prend en charge le traitement
  1. concurrence非阻塞I/O. demandes reçues et demandes proactives. Il n’est pas nécessaire d’utiliser le multi-threading ou le multi-processus.
  2. Cette question est encore préfigurante. Ces questions sont très sèches. Vous pouvez y réfléchir. Les réponses aux questions 2 et 3 sont toutes placées dans un script : nio_server.php

. Dans le code ci-dessus, j'ai répertorié une entreprise spécifique, c'est-à-dire que l'utilisateur demande une action d'achat supplémentaire dans le panier. Quant au service de panier, il doit interagir avec les services de produits, les services d'inventaire et les services préférentiels pour vérifier le. faisabilité de l'action d'achat complémentaire. Il existe des méthodes synchrones et asynchrones pour demander et comparer.

Il y a beaucoup de code à suivre, j'ai mis des liens gitee. Pour l'utilisation, voir readme.md

La dernière question : en PHP, si vous écrivez du code de manière synchrone, le programme sera-t-il exécuté de manière asynchrone ? Comment ajuster le code.
  1. Astuce : Ceci est lié à la syntaxe
de

. PHPyieldAutre rappel :

Quelles sont les caractéristiques grammaticales,

in et outyield !En regardant notre code,

synchrone, asynchrone, in et out

A quoi as-tu pensé ? Lorsque vous voyez le code, en mode de traitement synchrone, ces trois fonctions

checkInventory lancent une requête et attendent tour à tour les résultats renvoyés. Une fois ces trois fonctions exécutées, répondez à. demande le client. checkProduct

异步处理模式下,这三个函数发起请求完毕后,代码就跳出循环了,然后是在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(&#39;tcp://0.0.0.0:8080&#39;);// 设置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(&#39;get request msg :&#39; . $re . PHP_EOL );

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

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

        CoWorker::safeEcho(&#39;Response to :&#39; . $conName . PHP_EOL . PHP_EOL);
    } catch (ConnectionCloseException $e) {
        CoWorker::safeEcho(&#39;Connection closed, &#39; . $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

工作原理

先上图:

La partie supérieure de l'image est le diagramme des voies de travail de Workerman, et la partie inférieure de l'image est le diagramme des voies de travail de CoWorkerman. Lorsque le

dans

workermanworker进程 rencontre une fonction de blocage, il attendra le retour d'IO. S'il y a une nouvelle demande à ce moment-là, le travailleur inactif sera en compétition pour cette nouvelle connexion.

J'ai décrit une situation d'utilisation AsyncTCPConnection dans worker5 ci-dessus. Une requête non bloquante a été lancée au sein du travailleur et une fonction de rappel a été enregistrée, puis le programme a continué à s'exécuter jusqu'à la fin. Lorsqu'une requête asynchrone répond, vous devez répondre d'une autre manière (par exemple en lançant une autre requête pour informer le demandeur).

Dans l'image ci-dessous CoWorkerman, il y a également plusieurs Workers en compétition pour de nouvelles demandes. Lorsque le travailleur 1 reçoit une nouvelle demande, un générateur sera généré, une demande asynchrone sera lancée dans le générateur et une réponse. le rappel sera enregistré. Après avoir répondu, retournez à l'endroit où le générateur a sauté (yield) et continuez à exécuter le code.

Initier une requête asynchrone et enregistrer une fonction de rappel. Ces tâches par défaut ont été effectuées dans le cadre CoWorkerman Le travail dans la fonction de rappel est le suivant : recevoir des données et les envoyer au générateur qui a initié. la demande.

Dans cet exemple, plusieurs requêtes sont lancées en appelant Promise:all() et les résultats sont renvoyés. Attendez que toutes les réponses soient renvoyées avant de continuer à exécuter le générateur

<.>
Après la sortie du programme

, le travailleur est dans l'état de boucle d'événements (yield), c'est-à-dire une surveillance multicanal : port de requête, port de réponse à la requête d'un client tiers. À ce moment-là, si : $event->loop()

    a une nouvelle demande, il entrera en compétition avec d'autres
  1. pour les nouvelles demandes. Si une concurrence se produit, un nouveau générateur sera généré dans le travailleur. worker
  2. Si le client répond, appelez la fonction de rappel.
  3. Si le client répond, continuez à exécuter le programme générateur.
A partir de 1, on peut supposer que s'il n'y a qu'un seul

, alors le Worker peut continuer à accepter et traiter la demande suivante si la demande précédente n'est pas complétée. Autrement dit, Worker peut s'exécuter sous un seul CoWorkerman et gérer plusieurs requêtes simultanément. Worker

Bien sûr, il y a un principe ici. Les fonctions de blocage ne peuvent pas être exécutées en mode

unique. Une fois bloquées, les requêtes suivantes seront bloquées sur la carte réseau. Par conséquent, à moins que vous ne connaissiez très bien votre code, si vous utilisez une bibliothèque tierce, je vous recommande toujours d'exécuter Worker dans plusieurs modes Worker Lors du blocage, il existe d'autres CoWorkerman pour contenir de nouvelles requêtes. Worker

La signification de CoWorkerman

    Utiliser du code synchrone pour lancer des requêtes asynchrones. Plusieurs requêtes peuvent être simultanées. Passer de l'attente série IO à l'attente. parallèlement pour réduire les temps d’attente intrépides. Améliorez l’efficacité des programmes métier sans réduire la lisibilité du code.
  1. Traitez autant de requêtes que possible via la boucle d'événements dans un seul thread, ce qui atténue les changements de thread fréquents provoqués par une requête et un thread, et améliore l'efficacité opérationnelle du cœur.

Niche CoWorkerman

convient aux applications qui gèrent des demandes

pures, telles que Socket, ou Workerman Gateway intégrant plusieurs services大前端Le résultat, après synthèse, est renvoyé à un scénario comme RPC.前三页

La journalisation est l'exigence la plus fondamentale de chaque programme. Puisque la fonction d'écriture de fichier est bloquante, il est recommandé d'utiliser un message. file d'attente ou file d'attente redis. Ou sautez

et lancez Logstash directement Elasticsearch

CoWorkerman a ses limites et sa propre position.

Résumé

D'accord~ C'est la fin du codage coroutine PHP vers le codage asynchrone en réseau. Si vous avez de nombreux doutes après avoir lu cet article, veuillez laisser un message. message pour poser des questions. , si

ne se souvient pas bien de la grammaire, vous pouvez lire les articles précédents de cette série pour les revoir. yield

Si oui, veuillez le faire trois fois.

Merci ! CoWorkerman

Apprenez lutilisation avancée de PHP Yield

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer