Upgrade to Pro — share decks privately, control downloads, hide ads and more …

En Route Vers 
Le Multi-Tâche

En Route Vers 
Le Multi-Tâche

Depuis très longtemps, en tant que développeur PHP, je me demande s'il est possible de faire du multi-tâche en PHP. Il s'avère que la réponse à cette question n'est pas aussi simple que ce que l'on peut croire. En effet, répondre catégoriquement "non" à cette question peut sembler correct car PHP n'a pas été conçu comme un langage permettant de lancer des traitement en parallèle. Mais que diriez-vous si je vous démontrais que l'on peut finalement arriver assez facilement à faire des traitements multi-tâches en PHP et ainsi simuler une sorte d'asynchronisme ? Facile ! Me direz-vous, aujourd'hui nous avons tout un tas de logiciels et librairies à notre disposition pour le faire :

* AMQP,
* ReactPHP,
* Les sous-processus,
* PThread,
* ...

Et je répondrais que la majorité de ces solutions ne sont pas si simples à mettre en oeuvre que ce qu'on pense. Par contre, les générateurs, eux, sont intégrés nativement à PHP, ne nécessitent aucune extension ou infrastructure et peuvent nous permettre d'arriver à un résultat qui peut être, dans certains cas, satisfaisant.

#php #phptour #afup

## Liens

* async-interop/awaitable : https://github.com/async-interop/awaitable
* jubianchi/async-generator : https://github.com/jubianchi/async-generator
* Cooperative multitasking using coroutines (in PHP!) : https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html

Julien BIANCHI

May 24, 2016
Tweet

More Decks by Julien BIANCHI

Other Decks in Programming

Transcript

  1. DISCLAIMER • Pas d’images, même pas de petits chats •

    Les solutions présentées ne sont ni bonnes, ni 
 mauvaises • On va parler de run-loop et de générateurs
  2. DISCLAIMER • Je fais aussi du JS et j’aime l’ES6

    • J’ai fait de l’Objective-C (avec GCD — libdispatch) • Je maintiens aussi du Java BIS
  3. LES WORKERS ✔ Traiter des lots de manière asynchrone ✔

    Très adaptés pour des actions « finales » ✔ Éventuellement, en parallèle ✔ Répartition de la charge sur n machines
  4. LES WORKERS ✖ Infrastructure lourde ✖ Ne partage rien avec

    le processus principal ✖ Communication à travers le réseau ✖ Compliqué d’orchestrer plusieurs tâches
  5. LES SOUS-PROCESSUS ✔ Code natif PHP ✔ Permet d’exploiter tous

    les CPUs disponibles ✔ Pas d’extension : proc_open / stream_select
  6. LES SOUS-PROCESSUS ✖ Shell Out : peu portable ✖ Ne

    partage rien avec le processus principal ✖ Nécessite de définir un protocol pour l’IPC ✖ Compliqué d’orchestrer plusieurs processus
  7. REACTPHP ✔ Code natif PHP ✔ Simple d’utilisation ✔ Une

    « vraie » run-loop ✔ Plusieurs librairies disponibles (promise, http, …)
  8. REACTPHP ✖ Une run-loop globale ✖ Une dépendance en plus

    pour chaque besoin (socket, 
 stream, child-process, …)
  9. LES GÉNÉRATEURS — PHP.net - Résumé sur les générateurs
 http://php.net/manual/fr/language.generators.overview.php

    Les générateurs fournissent une façon simple de mettre en place des itérateurs sans le coût ni la complexité du développement d'une classe qui implémente l'interface Iterator.
  10. Le mot clé yield est le cœur d'une fonction générateur.

    Dans sa forme la plus simple, une instruction yield ressemble à une instruction return, excepté qu'au lieu de stopper l'exécution de la fonction et de retourner, yield fournit une valeur au code parcourant le générateur, et met en pause l'exécution de la fonction générateur. — PHP.net - Le mot-clé yield
 http://php.net/manual/fr/language.generators.syntax.php#control-structures.yield LES GÉNÉRATEURS
  11. Le mot clé yield est le cœur d'une fonction générateur.

    Dans sa forme la plus simple, une instruction yield ressemble à une instruction return, excepté qu'au lieu de stopper l'exécution de la fonction et de retourner, yield fournit une valeur au code parcourant le générateur, et met en pause l'exécution de la fonction générateur. — PHP.net - Le mot-clé yield
 http://php.net/manual/fr/language.generators.syntax.php#control-structures.yield LES GÉNÉRATEURS
  12. LES GÉNÉRATEURS function await(!!"$generators) { while(count($generators) > 0) { $generator

    = current($generators); $key = key($generators); $generator!#next(); /* on avance dans le générateur */ /* - Si le générateur est terminé, on stocke sa valeur de retour */ /* et on le supprime de la liste */ /* - Sinon, on passe au suivant */ if ($generator!#valid() === false) { unset($generators[$key]); $results[$key] = $generator!#getReturn(); } } ksort(results); return $results; } EXEMPLE
  13. LES GÉNÉRATEURS use function jubianchi\async\runtime\{await, all}; use function jubianchi\async\time\{delay}; $start

    = microtime(true); var_dump( await( all( delay(5000, 'Hello'), delay(2000, 'World!') ) ) ); echo 'Time spent: ' . (microtime(true) - $start) . PHP_EOL; CONCURRENCE
  14. LES GÉNÉRATEURS $ php time.php array(2) { [0] !" string(5)

    "Hello" [1] !" string(6) "World!" } Time spent: 5.0027248859406 CONCURRENCE
  15. LES GÉNÉRATEURS function read($stream, callable $data) : \generator { do

    { $read = [$stream]; $write = $except = null; $select = stream_select($read, $write, $except, 0, 1); if ($select > 0) { $buffer = stream_get_contents($read[0]); $data($buffer); } yield; $metadata = stream_get_meta_data($stream); } while ($metadata['eof'] === false &' $metadata['unread_bytes'] > 0); } ASYNC I/O
  16. LES GÉNÉRATEURS $select = stream_select($read, $write, $except, 0, 1); /*

    Ou */ $select = socket_select($read, $write, $except, 0, 1); ASYNC I/O Surveiller la modification d'un ou plusieurs flux yield; Mettre le traitement en pause
  17. LES GÉNÉRATEURS use function runtime\{await, all}; use function stream\{read}; $onData

    = function($data) { /* handle file contents */ }; await( all( read(fopen(__DIR__ . '/data/d1.dat', 'r'), $onData), read(fopen(__DIR__ . '/data/d2.dat', 'r'), $onData) ) ); ALL
  18. LES GÉNÉRATEURS use function runtime\{await, race}; use function stream\{read}; $onData

    = function($data) { /* handle file contents */ }; await( race( read(fopen('http://us.server.com/data/d1.dat', 'r'), $onData), read(fopen('http://us2.server.com/data/d1.dat', 'r'), $onData) ) ); RACE
  19. LES GÉNÉRATEURS use function runtime\{await, all, race}; use function stream\{read};

    $onData = function($data) { /* handle file contents */ }; await( all( race( read(fopen('http://us.server.com/data/d1.dat', 'r'), $onData), read(fopen('http://us2.server.com/data/d1.dat', 'r'), $onData) ), race( read(fopen('http://us.server.com/data/d2.dat', 'r'), $onData), read(fopen('http://us2.server.com/data/d2.dat', 'r'), $onData) ) ) ); FREESTYLE
  20. LES GÉNÉRATEURS use function runtime\{await, all}; use function runtime\socket\{write}; use

    function runtime\pipe\{make}; use function runtime\stream\{tail}; use function runtime\loop\{endless}; $pipe = make(); $socket = socket_create(/* … */); /* … */ await( all( tail(fopen(‘first.log', 'r'), $pipe), tail(fopen(‘second.log', 'r'), $pipe), endless(function () use () { $data = yield from $pipe!#dequeue(); yield from write($socket, $data); ) ) ); PIPES
  21. LES GÉNÉRATEURS ✔ Code natif PHP ✔ Pas d’extension ✔

    Librairie très légère : uniquement quelques 
 fonctions ✔ Peut être utilisé localement ✔ Très adapté pour manipuler simultanément des 
 flux (socket, fichier, …)
  22. LES GÉNÉRATEURS ✖ Détournement des générateurs ✖ Uniquement pour des

    traitements locaux ✖ Adapté pour manipuler des flux ✖ PHP ^7.0.0
  23. LES GÉNÉRATEURS $ php http.php 1337 & $ siege -r

    2 -c 250 http://127.0.0.1:1337 Transactions: 500 hits Availability: 100.00 % Elapsed time: 3.79 secs Data transferred: 0.01 MB Response time: 1.42 secs Transaction rate: 131.93 trans/sec Throughput: 0.00 MB/sec Concurrency: 186.92 Successful transactions: 500 Failed transactions: 0 Longest transaction: 1.51 Shortest transaction: 1.21 NUMBERS