Maison > développement back-end > tutoriel php > Introduction détaillée aux coroutines en php (code)

Introduction détaillée aux coroutines en php (code)

不言
Libérer: 2023-04-04 07:24:02
original
3916 Les gens l'ont consulté

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.

Rendement et générateur

Générateur

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.
Copier après la connexion

fooLa 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");
}
Copier après la connexion

yield

yieldLe 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.phpLe script présente trois formes de valeurs de retour de rendement :

  1. yield $key => $value : renvoie la clé et la valeur des données

  2. yield $value : renvoie les données, la clé est attribuée par le système

  3. yield : renvoie la valeur nulle, la clé est attribuée par le système ; 🎜>

    Autoriser la fonction à mettre en pause, reprendre l'exécution à tout moment et renvoyer les données à l'appelant. Si des données externes sont nécessaires pour poursuivre l'exécution, ce travail est assuré par la fonction
  4. du générateur : la variable qui apparaît à gauche de
recevra la valeur transmise par

. Examinons un exemple courant d'utilisation de la fonction yield : sendyieldsendsend permet une communication de données bidirectionnelle entre les générateurs et le monde extérieur :

renvoie des données
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!');
Copier après la connexion
fournit un fonctionnement continu prenant en charge ; données. Puisque

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. sendnextLes autres expressions nextsend(null)

ne sont pas valides avant PHP7 et doivent être mises entre crochets :
    ;
  1. $string = yield $data;$string = (yield $data) La fonction du générateur PHP5 ne peut pas avoir de valeur

    Après PHP7, elle peut renvoyer une valeur et obtenir la valeur renvoyée via
  2. du générateur.
  3. returngetReturnPHP7 ajoute une nouvelle syntaxe

    pour implémenter la délégation du générateur.
  4. yield fromLe générateur est un itérateur unidirectionnel et

    ne peut pas être appelé après son démarrage.
  5. rewindRésumé

  6. Par rapport à d'autres itérateurs, les générateurs ont les caractéristiques d'une faible surcharge de performances et d'un codage facile. Son rôle se reflète principalement sous trois aspects :

Génération de données (producteur), restitution des données via le rendement

  1. Consommation de données (consommateur), consommer les données envoyées par send

  2. implémente la coroutine.

  3. Concernant les générateurs et l'utilisation de base en PHP, il est recommandé de lire le billet du blog du patron de

    2gua
  4. : PHP Generator, vivant, intéressant et facile à comprendre.

Programmation 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 coroutines

yieldLes 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.

Les coroutines peuvent être considérées comme des « threads en mode utilisateur », qui nécessitent que les programmes utilisateur implémentent la planification. Les threads et les processus sont programmés par le système d'exploitation pour s'exécuter alternativement de manière « préemptive », et les coroutines abandonnent activement le processeur pour s'exécuter alternativement de manière « négociative ». Les coroutines sont très légères, la commutation de coroutines n'implique pas de changement de thread et l'efficacité d'exécution est élevée. Plus le nombre est grand, meilleurs sont les avantages des coroutines.

Générateurs et coroutines

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.

Programmation coroutine

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.

Résumé

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!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal