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

F46b6942be65359575174569d8979223?s=128

Julien BIANCHI

May 24, 2016
Tweet

Transcript

  1. EN ROUTE VERS 
 LE MULTI-TÂCHE

  2. 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
  3. @JUBIANCHI contact@jubianchi.fr DT @PMSIpilot Contributor on @atoum_org and @hoaproject

  4. 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
  5. LE MULTI-TÂCHE PARALLELES

  6. LE MULTI-TÂCHE CONCURRENTES

  7. PHP & LE MULTI-TÂCHE ☹ Nope !

  8. PHP & LE MULTI-TÂCHE En fait si, mais pas nativement

    !
  9. PHP & LE MULTI-TÂCHE Les Workers PThread Les Sous-Processus ReactPHP

    Icicle
  10. LES WORKERS

  11. LES WORKERS broker queue consumer producer job channel exchange PubSub

  12. 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
  13. LES WORKERS ✖ Infrastructure lourde ✖ Ne partage rien avec

    le processus principal ✖ Communication à travers le réseau ✖ Compliqué d’orchestrer plusieurs tâches
  14. LES SOUS-PROCESSUS

  15. LES SOUS-PROCESSUS ✔ Code natif PHP ✔ Permet d’exploiter tous

    les CPUs disponibles ✔ Pas d’extension : proc_open / stream_select
  16. 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
  17. PTHREAD

  18. PTHREAD ✔ Code natif PHP ✔ Simple d’utilisation ✔ Synchronisation

  19. PTHREAD ✖ Nécessite une extension PHP ✖ Serialisation du contexte

    ✖ Peu de ressources et librairies
  20. REACTPHP

  21. REACTPHP ✔ Code natif PHP ✔ Simple d’utilisation ✔ Une

    « vraie » run-loop ✔ Plusieurs librairies disponibles (promise, http, …)
  22. REACTPHP delay(5000, 'Hello') delay(2000, 'World') Tu as fini ? RUN-LOOP

  23. delay(5000, 'Hello') delay(2000, 'World') Non ! REACTPHP RUN-LOOP

  24. delay(5000, 'Hello') delay(2000, 'World') Tu as fini ? REACTPHP RUN-LOOP

  25. delay(5000, 'Hello') delay(2000, 'World') Non ! REACTPHP RUN-LOOP

  26. delay(5000, 'Hello') delay(2000, 'World') Tu as fini ? REACTPHP RUN-LOOP

  27. delay(5000, 'Hello') delay(2000, 'World') Oui ! REACTPHP RUN-LOOP

  28. 'Hello' delay(2000, 'World') REACTPHP RUN-LOOP

  29. 'Hello' delay(2000, 'World') Tu as fini ? REACTPHP RUN-LOOP

  30. 'Hello' delay(2000, 'World') Non ! REACTPHP RUN-LOOP

  31. 'Hello' delay(2000, 'World') Tu as fini ? REACTPHP RUN-LOOP

  32. 'Hello' delay(2000, 'World') Oui ! REACTPHP RUN-LOOP

  33. 'World' 'Hello' REACTPHP RUN-LOOP

  34. REACTPHP ✖ Une run-loop globale ✖ Une dépendance en plus

    pour chaque besoin (socket, 
 stream, child-process, …)
  35. ICICLE

  36. ICICLE ✔ ~= ReactPHP ✔ Awaitable (https://github.com/async-interop/ awaitable) ✔ Basé

    sur les générateurs
  37. LES GÉNÉRATEURS

  38. 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.
  39. LES GÉNÉRATEURS De simples itérateurs ?

  40. YIELD !

  41. 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
  42. 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
  43. LES GÉNÉRATEURS YIELD est la clé !

  44. Chaque yield est une opportunité pour changer de contexte et

    donc de tâche. LES GÉNÉRATEURS
  45. LES GÉNÉRATEURS var_dump( await( all( delay(5000, 'Hello'), delay(2000, 'World!') )

    ) ); EXEMPLE
  46. 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
  47. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Tu as fini ?

    CONCURRENCE / CONTEXT SWITCH
  48. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Non ! CONCURRENCE /

    CONTEXT SWITCH
  49. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Tu as fini ?

    CONCURRENCE / CONTEXT SWITCH
  50. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Non ! CONCURRENCE /

    CONTEXT SWITCH
  51. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Tu as fini ?

    CONCURRENCE / CONTEXT SWITCH
  52. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Non ! CONCURRENCE /

    CONTEXT SWITCH
  53. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Tu as fini ?

    CONCURRENCE / CONTEXT SWITCH
  54. LES GÉNÉRATEURS delay(5000, 'Hello') delay(2000, 'World') Oui ! CONCURRENCE /

    CONTEXT SWITCH
  55. LES GÉNÉRATEURS delay(5000, 'Hello') 'World' CONCURRENCE / CONTEXT SWITCH

  56. LES GÉNÉRATEURS delay(5000, 'Hello') Tu as fini ? 'World' CONCURRENCE

    / CONTEXT SWITCH
  57. LES GÉNÉRATEURS delay(5000, 'Hello') Non ! 'World' CONCURRENCE / CONTEXT

    SWITCH
  58. LES GÉNÉRATEURS delay(5000, 'Hello') Tu as fini ? 'World' CONCURRENCE

    / CONTEXT SWITCH
  59. LES GÉNÉRATEURS delay(5000, 'Hello') Oui ! 'World' CONCURRENCE / CONTEXT

    SWITCH
  60. LES GÉNÉRATEURS 'World' 'Hello' CONCURRENCE / CONTEXT SWITCH

  61. 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
  62. LES GÉNÉRATEURS $ php time.php array(2) { [0] !" string(5)

    "Hello" [1] !" string(6) "World!" } Time spent: 5.0027248859406 CONCURRENCE
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. LES GÉNÉRATEURS MORE!!! runtime\some time\delay time\throttle runtime\race runtime\all process\spawn stream\read

    stream\tail process\passthru loop\until loop\endless loop\times
  70. 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, …)
  71. LES GÉNÉRATEURS ✖ Détournement des générateurs ✖ Uniquement pour des

    traitements locaux ✖ Adapté pour manipuler des flux ✖ PHP ^7.0.0
  72. 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
  73. HTTPS://GITHUB.COM/JUBIANCHI/ASYNC-GENERATOR

  74. COOPERATIVE MULTITASKING USING COROUTINES 
 (IN PHP!) Nikita Popov HTTPS://NIKIC.GITHUB.IO/2012/12/22/COOPERATIVE-

    MULTITASKING-USING-COROUTINES-IN-PHP.HTML
  75. QUESTIONS ?

  76. @JUBIANCHI contact@jubianchi.fr DT @PMSIpilot Contributor on @atoum_org and @hoaproject MERCI

    ! https://joind.in/talk/eb500