Cet article présente d'abord le concept de générateur, en se concentrant sur l'utilisation du rendement et l'interface du générateur. La partie coroutine explique brièvement les principes des coroutines et les points auxquels il convient de prêter attention dans la programmation de coroutines PHP.
PHP a introduit le générateur (Generator) depuis la version 5.5, sur la base duquel la programmation de coroutines peut être réalisée. Cet article commence par un examen des générateurs, puis passe à la programmation coroutine.
Un générateur est un type de données qui implémente l'interface itérateur. L'instance du générateur ne peut pas être obtenue via new et il n'existe pas de méthode statique pour obtenir l'instance du générateur. La seule façon d'obtenir une instance de générateur est d'appeler la fonction générateur (une fonction contenant le mot-clé rendement). L’appel de la fonction générateur renvoie directement un objet générateur et le code de la fonction commence à s’exécuter lorsque le générateur est en cours d’exécution.
Accédez d'abord au code pour découvrir intuitivement le rendement et les générateurs :
# generator1.php function foo() { exit('exit script when generator runs.'); yield; } $gen = foo(); var_dump($gen); $gen->current(); echo 'unreachable code!'; # 执行结果 object(Generator)#1 (0) { } exit script when generator runs.
foo
La fonction contient le mot-clé yield
et se transforme en fonction génératrice. L’appel de foo
n’exécute aucun code dans le corps de la fonction, mais renvoie à la place une instance de générateur. Une fois le générateur exécuté, le code de la fonction foo
est exécuté et le script se termine.
Comme leur nom l'indique, les générateurs peuvent être utilisés pour générer des données. C'est juste que la façon dont il génère les données est différente des autres fonctions : le générateur renvoie les données via yield
au lieu de return
; après que yield
ait renvoyé les données, la fonction du générateur ne sera pas détruite, mais mettra simplement son fonctionnement en pause. Vous pouvez continuer à partir du point de pause dans le futur ; le générateur s'exécute une fois et (uniquement) renvoie une donnée. S'il est exécuté plusieurs fois, plusieurs données seront renvoyées. Si le générateur n'est pas appelé pour obtenir des données. Le code dans le générateur restera immobile. Le soi-disant mouvement à chaque fois, disons Voici à quoi ressemblent les données générées par le générateur.
Le générateur implémente l'interface itérateur. Vous pouvez utiliser une foreach
boucle ou un manuel current/next/valid
pour obtenir les données du générateur. Le code suivant illustre la génération et le parcours de données :
# generator2.php function foo() { # 返回键值对数据 yield "key1" => "value1"; $count = 0; while ($count < 5) { # 返回值,key自动生成 yield $count; ++ $count; } # 不返回值,相当于返回null yield; } # 手动获取生成器数据 $gen = foo(); while ($gen->valid()) { fwrite(STDOUT, "key:{$gen->key()}, value:{$gen->current()}\n"); $gen->next(); } # foreach 遍历数据 fwrite(STDOUT, "\ndata from foreach\n"); foreach (foo() as $key => $value) { fwrite(STDOUT, "key:$key, value:$value\n"); }
yield
Le mot-clé est le cœur du générateur, qui permet aux fonctions ordinaires de se différencier (d'évoluer) en fonctions de générateur. yield
signifie "abandonner". Lorsque le programme atteint l'instruction yield
, l'exécution sera suspendue, le processeur sera abandonné et le contrôle sera rendu à l'appelant, et l'exécution continuera à partir du point d'interruption la prochaine fois. il est exécuté. Lorsque le contrôle revient à l'appelant, l'instruction yield
peut renvoyer la valeur à l'appelant. generator2.php
Le script présente trois formes de valeurs de retour de rendement :
yield $key => $value : renvoie la clé et la valeur des données
. Examinons un exemple courant d'utilisation de la fonction yield
: send
yield
send
send
permet une communication de données bidirectionnelle entre les générateurs et le monde extérieur :
function logger(string $filename) { $fd = fopen($filename, 'w+'); while($msg = yield) { fwrite($fd, date('Y-m-d H:i:s') . ':' . $msg . PHP_EOL); } fclose($fd); } $logger = logger('log.txt'); $logger->send('program starts!'); // do some thing $logger->send('program ends!');
permet au générateur de continuer l'exécution, ce comportement est similaire à l'interface send
de l'itérateur, et yield
est équivalent à send
. send
next
Les autres expressions next
send(null)
$string = yield $data;
$string = (yield $data)
La fonction du générateur PHP5 ne peut pas avoir de valeur
return
getReturn
PHP7 ajoute une nouvelle syntaxe
yield from
Le générateur est un itérateur unidirectionnel et
rewind
Résumé
Génération de données (producteur), restitution des données via le rendement
Consommation de données (consommateur), consommer les données envoyées par send
implémente la coroutine.
Concernant les générateurs et l'utilisation de base en PHP, il est recommandé de lire le billet du blog du patron de
2guaProgrammation de coroutineLa coroutine est un sous-programme qui peut être interrompu et repris à tout moment. Le mot-clé
permet à la fonction d'avoir cette capacité, elle peut donc être utilisée pour la programmation de coroutine. . Processus, threads et coroutinesyield
Les threads appartiennent à des processus, et un processus peut avoir plusieurs threads. Un processus est la plus petite unité d’allocation informatique de ressources, et un thread est la plus petite unité de planification et d’exécution informatique. Les processus et les threads sont planifiés par le système d'exploitation.
La coroutine implémentée par le générateur est une coroutine sans pile, c'est-à-dire que la fonction du générateur n'a qu'un cadre de fonction, qui est attaché à la pile de l'appelant pendant l'exécution pour l'exécution. Contrairement à la puissante coroutine empilée, le générateur ne peut pas contrôler la direction du programme après sa mise en pause et ne peut rendre le contrôle que passivement à l'appelant ; le générateur ne peut s'interrompre que lui-même, pas la coroutine entière. Bien entendu, l'avantage du générateur est qu'il est très efficace (il suffit de sauvegarder le compteur du programme lors d'une pause) et simple à mettre en œuvre.
En parlant de programmation coroutine en PHP, je pense que la plupart des gens ont déjà lu ce billet de blog réimprimé (traduit) par Brother Niao : Utiliser des coroutines pour réaliser plusieurs fonctions dans la planification des tâches PHP. L'auteur original Nikic est le développeur principal de PHP, l'initiateur et l'implémenteur de la fonction générateur. Si vous souhaitez en savoir plus sur les générateurs et la programmation de coroutines basée sur ceux-ci, la RFC de Nikic sur les générateurs et les articles sur le site Web de Niaoge sont des lectures incontournables.
Examinons d'abord le fonctionnement des coroutines basées sur un générateur : les coroutines fonctionnent en collaboration, c'est-à-dire que les coroutines abandonnent activement le processeur pour réaliser une exécution alternée de plusieurs tâches (c'est-à-dire un multitâche simultané, mais pas parallèle) ; Le générateur peut être considéré comme une coroutine. Lorsque l'instruction yield
est exécutée, le contrôle du processeur est renvoyé à l'appelant et celui-ci continue d'exécuter d'autres coroutines ou d'autres codes.
Regardons la difficulté de comprendre le blog de frère Bird. Les coroutines sont très légères et des milliers de coroutines (générateurs) peuvent exister simultanément dans un système. Le système d'exploitation ne planifie pas les coroutines et le travail d'organisation de l'exécution des coroutines incombe aux développeurs. Certaines personnes ne comprennent pas la partie coroutine de l'article de Brother Niao car elle dit qu'il y a peu de programmation coroutine (écrire des coroutines signifie principalement écrire des fonctions de générateur), mais ils passent beaucoup de temps à implémenter un planificateur de coroutines (planificateur ou noyau : simule). le système d'exploitation et effectue une planification équitable sur toutes les coroutines. La pensée générale du développement PHP est la suivante : j'ai écrit ces codes, et le moteur PHP appellera mes codes pour obtenir les résultats attendus. La programmation coroutine nécessite non seulement d'écrire du code pour effectuer le travail, mais également d'écrire du code pour indiquer à ces codes quand travailler. Si vous ne comprenez pas bien la pensée de l’auteur, elle sera naturellement plus difficile à comprendre. Il doit être planifié par lui-même, ce qui est un défaut de la coroutine du générateur par rapport à la coroutine native (formulaire asynchrone/attente).
Maintenant que nous savons ce qu'est la coroutine, à quoi peut-elle servir ? La coroutine abandonne le CPU d'elle-même pour coopérer et utiliser efficacement le CPU. Bien sûr, le moment d'abandonner devrait être lorsque le programme est bloqué. Où le programme va-t-il se bloquer ? Le code en mode utilisateur bloque rarement et le blocage est principalement causé par des appels système. La majorité des appels système sont des E/S, le principal scénario d'application des coroutines est donc la programmation réseau. Afin de rendre le programme hautes performances et haute concurrence, le programme doit s'exécuter de manière asynchrone et non bloqué. Étant donné que l'exécution asynchrone nécessite des notifications et des rappels, l'écriture de fonctions de rappel ne peut éviter le problème de « l'enfer des rappels » : la lisibilité du code est mauvaise et le processus d'exécution du programme est dispersé entre les couches de fonctions de rappel. Il existe deux manières principales de résoudre l’enfer des rappels : la promesse et les coroutines. Les coroutines peuvent écrire du code de manière synchrone et sont recommandées dans la programmation réseau haute performance (intensive en IO).
Revenons sur la programmation coroutine en PHP. En PHP, la programmation coroutine est implémentée sur la base de générateurs. Il est recommandé d'utiliser des frameworks coroutine tels que RecoilPHP
et Amp
. Ces frameworks ont déjà écrit des planificateurs. Si vous développez une fonction génératrice directement dessus, le noyau planifiera automatiquement l'exécution (si vous souhaitez qu'une fonction soit planifiée pour être exécutée en mode coroutine, ajoutez simplement yield
dans le corps de la fonction). Si vous ne souhaitez pas utiliser la méthode yield
pour la programmation coroutine, nous vous recommandons swoole
ou son framework dérivé, qui peut obtenir une expérience de programmation coroutine de type Golang tout en profitant de l'efficacité de développement de PHP.
Si vous souhaitez utiliser la programmation coroutine PHP originale, un planificateur similaire à celui du blog de Niao Ge est indispensable. Le planificateur planifie l'exécution de la coroutine. Une fois la coroutine interrompue, le contrôle revient au planificateur. Par conséquent, le planificateur doit toujours être dans la boucle principale (d'événement), c'est-à-dire que lorsque le processeur n'exécute pas la coroutine, il doit exécuter le code du planificateur. Lorsqu'il s'exécute sans coroutine, le planificateur doit se bloquer pour éviter de consommer le CPU (le blog de Niao Ge utilise l'appel système select
intégré), attendre que l'événement arrive, puis exécuter la coroutine correspondante. Pendant l'exécution du programme, à l'exception du blocage du planificateur, la coroutine ne doit pas appeler les API de blocage pendant l'exécution.
Dans la programmation coroutine, le rôle principal de yield
est de transférer le contrôle sans se soucier de sa valeur de retour (en gros, la valeur renvoyée par yield
sera exécutée la prochaine fois Venez directement send
le moment venu). L'accent doit être mis sur le moment du transfert de contrôle et sur le fonctionnement de la coroutine.
Un autre point à noter est que les coroutines ont peu à voir avec l'asynchronie, cela dépend également de la prise en charge de l'environnement d'exploitation. Dans l'environnement d'exploitation PHP conventionnel, même si une promesse/coroutine est utilisée, elle est toujours bloquée de manière synchrone. Peu importe à quel point le framework coroutine est génial, sleep
il ne fonctionne tout simplement pas. Par analogie, même si JavaScript n’utilise pas de technologies promises/async, il est asynchrone et non bloquant.
Grâce aux générateurs et à Promise, une programmation coroutine similaire à await
peut être implémentée. Il existe de nombreux codes pertinents sur Github et ne seront pas présentés dans cet article.
Recommandations associées :
$_SERVER en PHP Introduction détaillée
Introduction détaillée à output_buffering en PHP, tutoriel outputbuffering_PHP
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!