Il y a dix ans, nous avions une pile LAMP classique: Linux, Apache, MySQL et PHP, qui fonctionnait en mode lent de mod_php. Le monde a changĂ©, et avec lui l'importance de la vitesse. PHP-FPM est apparu, ce qui a permis d'augmenter considĂ©rablement les performances des solutions en PHP, et de ne pas réécrire de toute urgence vers quelque chose de plus rapide.En parallĂšle, la bibliothĂšque ReactPHP a Ă©tĂ© dĂ©veloppĂ©e en utilisant le concept Event Loop pour traiter les signaux du systĂšme d'exploitation et prĂ©senter les rĂ©sultats des opĂ©rations asynchrones. Le dĂ©veloppement de l'idĂ©e de ReactPHP - AMPHP. Cette bibliothĂšque utilise la mĂȘme boucle d'Ă©vĂ©nement, mais prend en charge les coroutines, contrairement Ă ReactPHP. Ils vous permettent d'Ă©crire du code asynchrone qui ressemble Ă synchrone. C'est peut-ĂȘtre le cadre le plus actuel pour dĂ©velopper des applications asynchrones en PHP.
Mais la vitesse est de plus en plus nĂ©cessaire, les outils ne sont dĂ©jĂ pas suffisants, donc l'idĂ©e de programmation asynchrone en PHP est l'un des moyens d'accĂ©lĂ©rer le traitement des requĂȘtes et de mieux utiliser les ressources.C'est ce dont Anton Shabovta va parler (zloyusr) Est dĂ©veloppeur chez Onliner. ExpĂ©rience de plus de 10 ans: j'ai commencĂ© avec des applications bureautiques en C / C ++, puis je suis passĂ© au dĂ©veloppement web en PHP. Il Ă©crit des projets "Home" en C # et Python 3, et en PHP il expĂ©rimente DDD, CQRS, Event Sourcing, Async Multitasking.Cet article est basĂ© sur une transcription du rapport d'Anton sur PHP Russie 2019 . Nous y comprendrons les opĂ©rations bloquantes et non bloquantes en PHP, nous Ă©tudierons de l'intĂ©rieur la structure des boucles d'Ă©vĂ©nements et des primitives asynchrones, telles que Promise et coroutines. Enfin, nous dĂ©couvrirons ce qui nous attend dans ext-async, AMPHP 3 et PHP 8.Nous introduisons quelques dĂ©finitions. Pendant longtemps, j'ai essayĂ© de trouver une dĂ©finition exacte des opĂ©rations asynchrones et asynchrones, mais je n'ai pas trouvĂ© et Ă©crit la mienne.L'asynchronie est la capacitĂ© d'un systĂšme logiciel Ă ne pas bloquer le thread principal d'exĂ©cution.
Une opération asynchrone est une opération qui ne bloque pas le flux d'exécution d'un programme tant qu'elle n'est pas terminée.
Cela semble simple, mais vous devez d'abord comprendre quelles opérations bloquent le flux d'exécution.Opérations de blocage
PHP est un langage interpréteur. Il lit le code ligne par ligne, traduit dans ses instructions et exécute. Sur quelle ligne de l'exemple ci-dessous le code sera-t-il bloqué?public function update(User $user)
{
try {
$sql = 'UPDATE users SET ...';
return $this->connection->execute($sql, $user->data());
} catch (\PDOException $error) {
log($error->getMessage());
}
return 0;
}
Si nous nous connectons Ă la base de donnĂ©es via PDO, le thread d'exĂ©cution sera bloquĂ© sur la chaĂźne de requĂȘte SQL serveur: return $this->connection->execute($sql, $user->data());
.C'est parce que PHP ne sait pas combien de temps le serveur SQL traitera cette requĂȘte et s'il s'exĂ©cutera du tout. Il attend une rĂ©ponse du serveur et le programme n'a pas Ă©tĂ© exĂ©cutĂ© pendant tout ce temps.PHP bloque Ă©galement le flux d'exĂ©cution sur toutes les opĂ©rations d'E / S.- SystĂšme de fichiers :
fwrite
, file_get_contents
. - Bases de données :
PDOConnection
, RedisClient
. Presque toutes les extensions pour connecter une base de données fonctionnent par défaut en mode blocage. - Processus :
exec
, system
, proc_open
. Ce sont des opérations de blocage, car tout travail avec des processus est construit via des appels systÚme. - Travailler avec stdin / stdout :
readline
, echo
, print
.
De plus, l'exécution est bloquée sur les temporisateurs : sleep
, usleep
. Ce sont des opérations dans lesquelles nous disons explicitement au thread de s'endormir pendant un certain temps. PHP sera inactif tout ce temps.Client SQL asynchrone
Mais le PHP moderne est un langage à usage général, et pas seulement pour le Web comme PHP / FI en 1997. Par conséquent, nous pouvons écrire un client SQL asynchrone à partir de zéro. La tùche n'est pas la plus triviale, mais résoluble.public function execAsync(string $query, array $params = [])
{
$socket = stream_socket_client('127.0.0.1:3306', ...);
stream_set_blocking($socket, false);
$data = $this->packBinarySQL($query, $params);
socket_write($socket, $data, strlen($data));
}
Que fait un tel client? Il se connecte Ă notre serveur SQL, met le socket en mode non bloquant, emballe la requĂȘte dans un format binaire que le serveur SQL comprend, Ă©crit des donnĂ©es dans le socket.Puisque le socket est en mode non bloquant, l'opĂ©ration d'Ă©criture depuis PHP est rapide.Mais que reviendra-t-il Ă la suite d'une telle opĂ©ration? Nous ne savons pas ce que le serveur SQL rĂ©pondra. Le traitement de la demande peut prendre du temps ou pas du tout. Mais quelque chose doit ĂȘtre retournĂ©? Si nous utilisons PDO et appelons la update
requĂȘte sur le serveur SQL, nous sommes renvoyĂ©s affected rows
- le nombre de lignes modifiĂ©es par cette requĂȘte. Nous ne pouvons pas encore le retourner, donc nous promettons seulement un retour.Promettre
Il s'agit d'un concept issu du monde de la programmation asynchrone.Promise est un objet wrapper sur le résultat d'une opération asynchrone. De plus, le résultat de l'opération nous est encore inconnu.
Malheureusement, il n'y a pas de norme Promise unique et il n'est pas possible de transférer directement des normes du monde JavaScript vers PHP.Fonctionnement de Promise
Puisqu'il n'y a pas encore de résultat, nous pouvons seulement en établir callbacks
.
Lorsque des données sont disponibles, il est nécessaire d'exécuter un rappel onResolve
.
Si une erreur se produit, un rappel sera exécuté onReject
pour gérer l'erreur.
L'interface Promise ressemble Ă ceci.interface Promise
{
const
STATUS_PENDING = 0,
STATUS_RESOLVED = 1,
STATUS_REJECTED = 2
;
public function onResolve(callable $callback);
public function onReject(callable $callback);
public function resolve($data);
public function reject(\Throwable $error);
}
Promise a un statut et des méthodes pour définir des rappels et remplir ( resolve
) Promise avec des données ou error ( reject
). Mais il y a des diffĂ©rences et des variations. Les mĂ©thodes peuvent ĂȘtre appelĂ©es diffĂ©remment, ou Ă la place de mĂ©thodes distinctes pour Ă©tablir des rappels, resolve
et il reject
peut y en avoir une, comme dans AMPHP, par exemple.Souvent des techniques pour remplir Promise resolve
et reject
retirer dans un objet sĂ©parĂ© la fonction asynchrone Ă Ă©tat de stockage diffĂ©rĂ© . Elle peut ĂȘtre considĂ©rĂ©e comme une sorte d'usine pour Promise. C'est unique: un diffĂ©rĂ© fait une promesse.
Comment appliquer cela dans le client SQL si nous dĂ©cidons de l'Ă©crire nous-mĂȘmes?Client SQL asynchrone
Tout d'abord, nous avons créé Deferred, fait tout le travail avec les sockets, noté les données et renvoyé Promise - tout est simple.public function execAsync(string $query, array $params = [])
{
$deferred = new Deferred;
$socket = stream_socket_client('127.0.0.1:3306', ...);
stream_set_blocking($socket, false);
$data = $this->packBinarySQL($query, $params);
socket_write($socket, $data, strlen($data));
return $deferred->promise();
}
Lorsque nous avons Promise, nous pouvons par exemple:- définissez le rappel et obtenez ceux
affected rows
qui nous reviennent PDOConnection
; - gérer l'erreur, ajouter au journal;
- Relancez la requĂȘte si le serveur SQL rĂ©pond avec une erreur.
$promise = $this->execAsync($sql, $user->data());
$promise->onResolve(function (int $rows) {
echo "Affected rows: {$rows}";
});
$promise->onReject(function (\Throwable $error) {
log($error->getMessage());
});
La question demeure: nous avons défini le rappel, et qui appellera resolve
et reject
?Boucle d'événement
Il y a le concept de boucle d'Ă©vĂ©nement - une boucle d'Ă©vĂ©nement . Il est capable de traiter des messages dans un environnement asynchrone. Pour les E / S asynchrones, ce seront des messages du systĂšme d'exploitation que le socket est prĂȘt Ă lire ou Ă Ă©crire.Comment ça fonctionne.- Le client indique Ă Event Loop qu'il est intĂ©ressĂ© par une sorte de socket.
- La boucle d'événements interroge le systÚme d'exploitation via un appel systÚme
stream_select
: le socket est-il prĂȘt, toutes les donnĂ©es sont-elles Ă©crites, les donnĂ©es proviennent-elles de l'autre cĂŽtĂ©. - Si le systĂšme d'exploitation signale que le socket n'est pas prĂȘt, bloquĂ©, alors la boucle d'Ă©vĂ©nement rĂ©pĂšte la boucle.
- Lorsque le systĂšme d'exploitation notifie que le socket est prĂȘt, la boucle d'Ă©vĂ©nements renvoie le contrĂŽle au client et active (
resolve
ou reject
) Promise.
Nous exprimons ce concept dans le code: prenez le cas le plus simple, supprimez la gestion des erreurs et d'autres nuances, de sorte qu'il reste une boucle infinie. Ă chaque itĂ©ration, il interrogera le systĂšme d'exploitation sur les sockets qui sont prĂȘtes Ă lire ou Ă Ă©crire, et appellera un rappel pour une socket spĂ©cifique.public static function run()
{
while (true) {
stream_select($readSockets, $writeSockets, null, 0);
foreach ($readSockets as $i => $socket) {
call_user_func(self::readCallbacks[$i], $socket);
}
}
}
Nous complétons notre client SQL. Nous informons Event Loop que dÚs que les données du serveur SQL arrivent sur le socket avec lequel nous travaillons, nous devons mettre Deferred à l'état «terminé» et transférer les données du socket vers Promise.public function execAsync(string $query, array $params = [])
{
$deferred = new Deferred;
...
Loop::onReadable($socket, function ($socket) use ($deferred) {
$deferred->resolve(socket_read($socket));
});
return $deferred->promise();
}
La boucle d'événements peut gérer nos E / S et fonctionne avec des sockets . Que peut-il faire d'autre?- JavaScript
setTimeout
setInterval
â . N . Event Loop . - Event Loop .
process control
, .
Event Loop
L'écriture de votre boucle d'événement est non seulement possible, mais également nécessaire. Si vous souhaitez travailler avec PHP asynchrone, il est important d'écrire votre propre implémentation simple pour comprendre comment cela fonctionne. Mais en production, nous ne l'utiliserons bien sûr pas, mais nous prendrons des implémentations toutes faites: stables, sans erreur et éprouvées au travail.Il existe trois implémentations principales.ReactPHP . Le plus ancien projet, a commencé en PHP 5.3. Maintenant, la version minimale requise de PHP est 5.3.8. Le projet implémente la norme Promises / A du monde JavaScript.AMPHP . C'est cette implémentation que je préfÚre utiliser. La configuration minimale requise est PHP 7.0, et puisque la prochaine version est déjà 7.3. Il utilise des coroutines en plus de Promise.Swoole. Il s'agit d'un cadre chinois intéressant dans lequel les développeurs tentent de porter certains concepts du monde Go vers PHP. La documentation en anglais est incomplÚte, la plupart sur GitHub en chinois. Si vous connaissez la langue, allez-y, mais jusqu'à présent, j'ai peur de travailler.
ReactPHP
Voyons Ă quoi ressemblera le client en utilisant ReactPHP pour MySQL.$connection = (new ConnectionFactory)->createLazyConnection();
$promise = $connection->query('UPDATE users SET ...');
$promise->then(
function (QueryResult $command) {
echo count($command->resultRows) . ' row(s) in set.';
},
function (Exception $error) {
echo 'Error: ' . $error->getMessage();
});
Tout est presque le mĂȘme que nous avons Ă©crit: nous crĂ©ons onnection
et exécutons la demande. Nous pouvons définir le rappel pour traiter les résultats (retour affected rows
): function (QueryResult $command) {
echo count($command->resultRows) . ' row(s) in set.';
},
et rappel pour la gestion des erreurs: function (Exception $error) {
echo 'Error: ' . $error->getMessage();
});
à partir de ces rappels, vous pouvez créer des chaßnes longues-longues, car chaque résultat then
dans ReactPHP renvoie également Promise.$promise
->then(function ($data) {
return new Promise(...);
})
->then(function ($data) {
...
}, function ($error) {
log($error);
})
...
Il s'agit d'une solution à un problÚme appelé enfer de rappel. Malheureusement, dans l'implémentation de ReactPHP, cela conduit au problÚme «Promise hell», lorsque des rappels 10-11 sont nécessaires pour connecter correctement RabbitMQ . Il est difficile de travailler avec un tel code et de le corriger. J'ai rapidement réalisé que ce n'était pas le mien et je suis passé à AMPHP.Amphp
Ce projet est plus jeune que ReactPHP et promeut un concept diffĂ©rent - les coroutines . Si vous envisagez de travailler avec MySQL dans AMPHP, vous pouvez voir que c'est presque la mĂȘme chose que de travailler avec PDOConnection
PHP.$pool = Mysql\pool("host=127.0.0.1 port=3306 db=test");
try {
$result = yield $pool->query("UPDATE users SET ...");
echo $result->affectedRows . ' row(s) in set.';
} catch (\Throwable $error) {
echo 'Error: ' . $error->getMessage();
}
Ici, nous créons un pool, connectons et exécutons la demande. Nous pouvons gérer les erreurs via les erreurs habituelles try...catch
, nous n'avons pas besoin de rappels.Mais avant l'appel asynchrone, le mot-clé - apparaßt ici yield
.Générateurs
Le mot yield
- clé transforme notre fonction en générateur.function generator($counter = 1)
{
yield $counter++;
echo "A";
yield $counter;
echo "B";
yield ++$counter;
}
DÚs que l'interpréteur PHP rencontre des yield
fonctions dans le corps, il se rend compte qu'il s'agit d'une fonction de générateur. Au lieu de s'exécuter, un objet classe est créé lors de son appel Generator
.Les générateurs héritent de l'interface de l'itérateur.$generator = generator(1);
foreach ($generator as $value) {
echo $value;
}
while ($generator->valid()) {
echo $generator->current();
$generator->next();
}
Par conséquent, il est possible d'exécuter des cycles foreach
et while
et d' autres. Mais, plus intéressant, l'itérateur a des méthodes current
et next
. Passons-les en revue étape par étape.Exécutez notre fonction generator($counter = 1)
. Nous appelons la méthode du générateur current()
. La valeur de la variable sera retournée $counter++
.DÚs que nous exécutons le générateur next()
, le code ira au prochain appel à l'intérieur du générateur yield
. L'ensemble du code entre les deux yield
s'exécutera, et c'est cool. En continuant de faire tourner le générateur, nous obtenons le résultat.Coroutines
Mais le générateur a une fonction plus intéressante - nous pouvons envoyer des données au générateur de l'extérieur. Dans ce cas, ce n'est pas tout à fait un générateur, mais une coroutine ou une coroutine.function printer() {
while (true) {
echo yield;
}
}
$print = printer();
$print->send('Hello');
$print->send(' PHPRussia');
$print->send(' 2019');
$print->send('!');
Dans cette section du code, il est intéressant de noter qu'il while (true)
ne bloquera pas le flux d'exécution, mais sera exécuté une seule fois. Nous avons envoyé les données à Corutin et les avons reçues 'Hello'
. Envoyé plus - reçu 'PHPRussia'
. Le principe est clair.En plus d'envoyer des données au générateur, vous pouvez envoyer des erreurs et les traiter de l'intérieur, ce qui est pratique.function printer() {
try {
echo yield;
} catch (\Throwable $e) {
echo $e->getMessage();
}
}
printer()->throw(new \Exception('Ooops...'));
RĂ©sumer. Corutin est un composant d'un programme qui prend en charge l'arrĂȘt et la poursuite de l'exĂ©cution tout en maintenant l'Ă©tat actuel . Corutin se souvient de sa pile d'appels, des donnĂ©es qu'il contient et peut les utiliser Ă l'avenir.GĂ©nĂ©rateurs et promesse
Regardons le générateur et les interfaces Promise.class Generator
{
public function send($data);
public function throw(\Throwable $error);
}
class Promise
{
public function resolve($data);
public function reject(\Throwable $error);
}
Ils se ressemblent, Ă l'exception des noms de mĂ©thode diffĂ©rents. Nous pouvons envoyer des donnĂ©es et envoyer une erreur au gĂ©nĂ©rateur et Ă Promise.Comment cela peut-il ĂȘtre utilisĂ©? Ăcrivons une fonction.function recoil(\Generator $generator)
{
$promise = $generator->current();
$promise->onResolve(function($data) use ($generator) {
$generator->send($data);
recoil($generator);
};
$promise->onReject(function ($error) use ($generator) {
$generator->throw($error);
recoil($generator);
});
}
La fonction prend la valeur de courant du générateur: $promise = $generator->current();
.J'ai un peu exagéré. Oui, nous devons vérifier que la valeur actuelle qui nous est retournée est une sorte de instanceof
promesse. Si oui, alors nous pouvons lui demander un rappel. Il renvoie en interne les données au générateur lorsque Promise réussit et démarre récursivement la fonction recoil
. $promise->onResolve(function($data) use ($generator) {
$generator->send($data);
recoil($generator);
};
La mĂȘme chose peut ĂȘtre faite avec des erreurs. Si Promise a Ă©chouĂ©, par exemple, le serveur SQL a dit: «Trop de connexions», alors nous pouvons jeter l'erreur Ă l'intĂ©rieur du gĂ©nĂ©rateur et passer Ă l'Ă©tape suivante.Tout cela nous amĂšne au concept important du multitĂąche coopĂ©ratif.MultitĂąche coopĂ©ratif
Il s'agit d'un type de multitĂąche, dans lequel la tĂąche suivante n'est effectuĂ©e qu'aprĂšs que la tĂąche en cours se dĂ©clare explicitement prĂȘte Ă donner du temps processeur Ă d'autres tĂąches.Je rencontre rarement quelque chose de simple, comme travailler avec une seule base de donnĂ©es. Le plus souvent, dans le processus de mise Ă jour de l'utilisateur, vous devez mettre Ă jour les donnĂ©es dans la base de donnĂ©es, dans l'index de recherche, puis nettoyer ou mettre Ă jour le cache, puis envoyer 15 messages supplĂ©mentaires Ă RabbitMQ. En PHP, tout ressemble à ça.
Nous effectuons les opérations une par une: nous avons mis à jour la base de données, l'index, puis le cache. Mais par défaut, PHP bloque de telles opérations (E / S), donc si vous regardez attentivement, en fait, tout est ainsi.
Sur les parties sombres, nous avons bloqué. Ils prennent le plus de temps.Si nous travaillons en mode asynchrone, alors ces parties ne sont pas là , la chronologie d'exécution est intermittente.
Vous pouvez tout coller ensemble et faire des piĂšces une par une.
Ă quoi tout cela sert-il? Si vous regardez la taille de la chronologie, au dĂ©but, cela prend beaucoup de temps, mais dĂšs que nous la collons ensemble, l'application accĂ©lĂšre.Le concept mĂȘme de boucle d'Ă©vĂ©nement et de multitĂąche coopĂ©ratif a longtemps Ă©tĂ© utilisĂ© dans diverses applications: Nginx, Node.js, Memcached, Redis. Tous utilisent l'intĂ©rieur de la boucle d'Ă©vĂ©nements et sont construits sur le mĂȘme principe.Depuis que nous avons commencĂ© Ă parler des serveurs Web Nginx et Node.js, rappelons comment se dĂ©roule le traitement des requĂȘtes en PHP.Traitement des demandes en PHP
Le navigateur envoie une requĂȘte, il arrive au serveur HTTP derriĂšre lequel se trouve un pool de flux FPM. L'un des threads met cette requĂȘte en service, connecte notre code et commence Ă l'exĂ©cuter.
Lorsque la prochaine demande arrive, un autre thread FPM le récupérera, connectera le code et il sera exécuté.Ce schéma de travail présente des avantages .- Gestion simple des erreurs . Si quelque chose a mal tourné et qu'une des demandes est tombée, nous n'avons rien à faire - la prochaine viendra, et cela n'affectera pas son travail.
- Nous ne pensons pas à la mémoire . Nous n'avons pas besoin de nettoyer ou de surveiller la mémoire. à la prochaine demande, toute la mémoire sera effacée.
C'est un schĂ©ma sympa qui a fonctionnĂ© en PHP depuis le tout dĂ©but et qui fonctionne toujours avec succĂšs. Mais il y a aussi des inconvĂ©nients .- Limitez le nombre de processus . Si nous avons 50 threads FPM sur le serveur, dĂšs que la 51e requĂȘte arrive, il attendra que l'un des threads devienne libre.
- Coûts pour le changement de contexte . Le systÚme d'exploitation bascule les demandes entre les flux FPM. Cette opération au niveau du processeur est appelée Context Switch. Il coûte cher et exécute un grand nombre de mesures. Il faut sauvegarder tous les registres, la pile d'appels, tout ce qui se trouve dans le processeur, puis passer à un autre processus, charger ses registres et sa pile d'appels, y refaire quelque chose, basculer à nouveau, sauvegarder encore ... Longtemps.
Abordons la question diffĂ©remment - nous Ă©crirons un serveur HTTP en PHP lui-mĂȘme.Serveur HTTP asynchrone
Ăa peut ĂȘtre fait. Nous avons dĂ©jĂ appris Ă travailler avec des sockets en mode non bloquant, et une connexion HTTP est la mĂȘme socket. Ă quoi ressemblera-t-il et fonctionnera-t-il?Ceci est un exemple de dĂ©marrage de serveurs HTTP dans le cadre AMPHP.Loop::run(function () {
$app = new Application();
$app->bootstrap();
$sockets = [Socket\listen('0.0.0.0:80')];
$server = new Server($sockets, new CallableRequestHandler(
function (Request $request) use ($app) {
$response = yield $app->dispatch($request);
return new Response(Status::OK, [], $response);
})
);
yield $server->start();
});
Tout est assez simple: charger Application
et créer un pool de sockets (un ou plusieurs).Ensuite, nous démarrons notre serveur, le configurons Handler
, qui sera exécuté à chaque demande et envoyons la demande à la nÎtre Application
afin d'obtenir une réponse.La derniÚre chose à faire est de démarrer le serveur yield $server->start();
.Dans ReactPHP, il aura Ă peu prĂšs la mĂȘme apparence, mais il n'y aura que 150 rappels pour diffĂ©rentes options, ce qui n'est pas trĂšs pratique.ProblĂšmes
Il y a plusieurs problĂšmes avec l'asynchronie en PHP.Manque de normes . Chaque framework: Swoole, ReactPHP ou AMPHP, implĂ©mente sa propre interface Promise et ils sont incompatibles.AMPHP pourrait thĂ©oriquement interagir avec Promise de ReactPHP, mais il y a une mise en garde. Si le code de ReactPHP n'est pas trĂšs bien Ă©crit, et quelque part appelle ou crĂ©e implicitement une boucle d'Ă©vĂ©nement, il s'avĂšre que deux boucles d'Ă©vĂ©nement tourneront Ă l'intĂ©rieur.JavaScript a une norme Promises / A + relativement bonne qui implĂ©mente Guzzle. Ce serait bien si les frameworks le suivaient. Mais jusqu'Ă prĂ©sent, ce n'est pas le cas.Fuites de mĂ©moire. Lorsque nous travaillons en PHP dans le mode FPM habituel, nous ne pensons peut-ĂȘtre pas Ă la mĂ©moire. MĂȘme si les dĂ©veloppeurs d'une extension ont oubliĂ© d'Ă©crire du bon code, ont oubliĂ© d'exĂ©cuter Valgrind et quelque part Ă l'intĂ©rieur de la mĂ©moire, alors ça va - la prochaine demande sera effacĂ©e et recommencera. Mais en mode asynchrone, vous ne pouvez pas vous le permettre, car tĂŽt ou tard, nous tomberons simplement OutOfMemoryException
.Il est possible de réparer, mais c'est difficile et douloureux. Dans certains cas, Xdebug aide, dans d'autres, à analyser les erreurs qui ont causé OutOfMemoryException
.OpĂ©rations de blocage . Il est essentiel de ne pas bloquer la boucle d'Ă©vĂ©nements lorsque nous Ă©crivons du code asynchrone. L'application ralentit dĂšs que nous bloquons le flux d'exĂ©cution, chacune de nos coroutines commence Ă s'exĂ©cuter plus lentement.Le paquetage kelunik / loop-block aidera Ă trouver de telles opĂ©rations pour AMPHP . Il rĂšgle la minuterie sur un trĂšs petit intervalle. Si la minuterie ne fonctionne pas, nous sommes bloquĂ©s quelque part. Le package aide Ă trouver des emplacements de blocage, mais pas toujours: le blocage dans certaines extensions peut ne pas ĂȘtre remarquĂ©.Prise en charge de la bibliothĂšque: Cassandra, Influx, ClickHouse . Le principal problĂšme de tout PHP asynchrone est le support des bibliothĂšques. Nous ne pouvons pas utiliser les habituels PDOConnection
, les RedisClient
pilotes d' autres pour tout le monde - nous avons besoin d' implĂ©mentations non-bloquant. Ils doivent Ă©galement ĂȘtre Ă©crits en PHP en mode non bloquant, car les pilotes C fournissent rarement des interfaces pouvant ĂȘtre intĂ©grĂ©es dans du code asynchrone.L'expĂ©rience la plus Ă©trange que j'ai eue avec le pilote de la base de donnĂ©es Cassandra. Ils fournissent des opĂ©rationsExecuteAsync
, GetAsync
et d'autres, mais en mĂȘme temps, ils retournent un objet Future
avec une seule méthode get
qui bloque. Il est possible d'obtenir quelque chose de maniĂšre asynchrone, mais pour attendre le rĂ©sultat, nous bloquerons toujours toute notre boucle. Pour le faire diffĂ©remment, par exemple, par le biais de rappels, cela ne fonctionne pas. J'ai mĂȘme Ă©crit mon client pour Cassandra, car nous l'utilisons dans notre travail.Indication de type . C'est un problĂšme d'AMPHP et de corutine.class UserRepository
{
public function find(int $id): \Generator
{
$data = yield $this->db->query('SELECT ...', $id);
return User::fill($data);
}
}
S'il se produit dans une fonction yield
, il devient alors un générateur. à ce stade, nous ne pouvons plus spécifier les types de données de retour corrects.PHP 8
Qu'est-ce qui nous attend en PHP 8? Je vais vous parler de mes hypothÚses ou plutÎt de mes envies ( NDLR: Dmitry Stogov sait ce qui va réellement apparaßtre en PHP 8 ).Boucle d'événement Il y a une chance qu'il apparaisse, car des travaux sont en cours pour apporter une boucle d'événement sous une forme quelconque au noyau. Si cela se produit, nous aurons une fonction await
, comme en JavaScript ou C #, qui nous permettra d'attendre le résultat de l'opération asynchrone à un certain endroit. Dans ce cas, nous n'aurons pas besoin d'extensions, tout fonctionnera de maniÚre asynchrone au niveau du noyau.
class UserRepository
{
public function find(int $id): Promise<User>
{
$data = await $this->db->query('SELECT ...', $id);
return User::fill($data);
}
}
Génériques Go attend les génériques, nous attendons les génériques, tout le monde attend les génériques.class UserRepository
{
public function find(int $id): Promise<User>
{
$data = yield $this->db->query('SELECT ...', $id);
return User::fill($data);
}
}
Mais nous n'attendons pas Generics pour les collections, mais pour indiquer que le résultat de Promise sera exactement l'objet User.Pourquoi tout ça?
Pour la vitesse et les performances.
PHP est un langage dans lequel la plupart des opérations sont liées aux E / S. Nous écrivons rarement du code qui est significativement lié aux calculs dans le processeur. TrÚs probablement, nous travaillons avec des sockets: nous devons faire une demande à la base de données, lire quelque chose, retourner une réponse, envoyer un fichier. L'asynchronie vous permet d'accélérer un tel code. Si nous regardons le temps de réponse moyen pour 1 000 demandes, nous pouvons accélérer d'environ 8 fois et de 10 000 demandes de prÚs de 6!Le 13 mai 2020, nous nous réunirons pour la deuxiÚme fois à PHP Russie pour discuter du langage, des bibliothÚques et des cadres, des façons d'augmenter la productivité et des piÚges des solutions de battage médiatique. Nous avons accepté les 4 premiers rapports , mais l'appel à communications arrive toujours. Postulez si vous souhaitez partager votre expérience avec la communauté.