La programmation asynchrone... et les pates

Post on 13-Dec-2014

896 views 0 download

description

La vidéo de la conférence au Forum PHP 2013 / AFUP : https://www.youtube.com/watch?v=WygFJHEloq0

Transcript of La programmation asynchrone... et les pates

LA PROGRAMMATION ASYNCHRONE ET LES PÂTES

François Zaninotto - @francoisz - marmelab.com

PARLONS

CUISINE

Pour 4 personnes Préparation et cuisson: 20 minutes Ingrédients: - 500g de spaghetti - 6 tomates bien mûres - 1 oignon - 1 carotte - 2 gousses d'ail - 1 branche de céleri - huile d'olive - sauge, romarin, basilic frais - sel et poivre - amour

Spaghetti à la tomate de Giuseppina Plat principal - Facile - Bon marché

1. Faire bouillir une grande quantité d'eau (non salée) dans une casserole

2. Pendant ce temps, pelez les tomates et coupez-les grossièrement

3. Epluchez et émincez l'oignon, la carotte, l'ail et le céleri

4. Lorsque l'eau bout, salez-la, puis déposez les spaghettis en couronne

5. Faire chauffer l'huile d'olive dans une sauteuse. Mettez-y à brunir la deuxième gousse d'ail préalablement épluchée.

6. Retirez la gousse d'ail, puis versez le mirepoix (mélange de légumes) et faites revenir à feu vif

7. Ajoutez les tomates, les herbes, salez et poivrez copieusement. Faites chauffer à feu moyen pendant 5 minutes

8. Goûtez régulièrement les spaghettis. Lorsqu'ils sont cuits al dente, égouttez-les puis déposez-les dans un plat chaud.

9. Versez la sauce tomate immédiatement sur les pâtes fumantes. Ajoutez le basilic grossièrement découpé.

10.Servez avec du parmesan rapé et un bon Chianti Classico, et régalez-vous

Spaghetti à la tomate de Giuseppina Plat principal - Facile - Bon marché

MAINTENANT LE CHEF C’EST

PHP

//  time  is  0  $pastaPan  =  new  Pan();  $water  =  new  Water();  $pastaPan-­‐>fill($water);  $pastaPan-­‐>warm($duration  =  10);  //  now  time  is  10  $pastaPan-­‐>fill(new  Spaghetti());  $pastaPan-­‐>warm($duration  =  8);  //  now  time  is  18  $pastaPan-­‐>remove($water);  !$saucePan  =  new  Pan();  $saucePan-­‐>fill(new  OliveOil());  $saucePan-­‐>warm($duration  =  2);  //  now  time  is  20  $saucePan-­‐>fill(MirepoixFactory::create($withGarlic  =  true));  $saucePan-­‐>warm($duration  =  5);  //  now  time  is  25  $saucePan-­‐>fill(TomatoFactory::create());  $saucepan-­‐>warm($duration  =  4);  //  now  time  is  29  !$plate  =  new  Plate();  $plate-­‐>addContentsOf($pastaPan);  $plate-­‐>addContentsOf($saucePan);  $plate-­‐>serve('Régalez-­‐vous');

LES 3 GRANDS TABOUS DU DEVELOPPEUR

UTILISER eval()

INJECTER UN CONTENEUR D’INJECTION DE DÉPENDANCE

LES 3 GRANDS TABOUS DU DEVELOPPEUR

SERVIR DES PÂTES FROIDES

LES 3 GRANDS TABOUS DU DEVELOPPEUR

2/10 NOTE DES INVITÉS

EVENTLOOP À LA RESCOUSSE

class  EventLoop  {      protected  $tick  =  0;      protected  $callbacksForTick  =  array();  !    public  function  executeLater($delay,  $callback)  {          $this-­‐>callbacksForTick[$this-­‐>tick  +  $delay]  []=  $callback;      }          public  function  start()  {          while  ($this-­‐>callbacksForTick)  {              $this-­‐>tick++;              $this-­‐>executeCallbacks();          }      }            public  function  executeCallbacks()  {          echo  "tic-­‐tac  :  "  .  $this-­‐>tick  .  "\n";          if  (!isset($this-­‐>callbacksForTick[$this-­‐>tick]))  {              return;  //  no  callback  to  execute          }          foreach  ($this-­‐>callbacksForTick[$this-­‐>tick]  as  $callback)  {              call_user_func($callback);          }          //  clean  up          unset($this-­‐>callbacksForTick[$this-­‐>tick]);      }  }

LA CASSEROLE

ASYNCHRONE

class  AsynchronousPan  extends  Pan  {      protected  $eventLoop;            public  function  __construct(EventLoop  $eventLoop)      {          $this-­‐>eventLoop  =  $eventLoop;      }            public  function  warm($duration,  $callback)      {          $this-­‐>eventLoop-­‐>executeLater($duration,  $callback);      }  }

$eventLoop  =  new  EventLoop();  !

$pan  =  new  AsynchronousPan($eventLoop);  $pan-­‐>warm(10,  function()  {      echo  "Régalez-­‐vous\n";  });  !

echo  "Ca  chauffe  !\n";  !

$eventLoop-­‐>start();

DÉMONSTRATION

Ca  chauffe  !  tic-­‐tac  :  1  tic-­‐tac  :  2  tic-­‐tac  :  3  tic-­‐tac  :  4  tic-­‐tac  :  5  tic-­‐tac  :  6  tic-­‐tac  :  7  tic-­‐tac  :  8  tic-­‐tac  :  9  tic-­‐tac  :  10  Régalez-­‐vous

LES SPAGHETTI

ASYNCHRONES

$eventLoop  =  new  EventLoop();  !

$plate  =  new  Plate();  !

//  pasta  $pastaPan  =  new  AsynchronousPan($eventLoop);  $water  =  new  Water();  $pastaPan-­‐>fill($water);  echo  "pastaPan:  Allumage\n";  $pastaPan-­‐>warm($duration  =  10,  function()  use  ($pastaPan,  $plate,  $water)  {      echo  "pastaPan:  L'eau  bout\n";      $pastaPan-­‐>fill(new  Spaghetti());      echo  "pastaPan:  Lancement  de  la  cuisson  des  spaghettis\n";      $pastaPan-­‐>warm($duration  =  8,  function()  use  ($pastaPan,  $plate,  $water){          echo  "pastaPan:  Les  spaghettis  sont  prêts\n";          $pastaPan-­‐>remove($water);          $plate-­‐>addContentsOf($pastaPan);      });  });  ../..

../..  //  sauce  $eventLoop-­‐>executeLater($delay  =  7,  function()  use  ($plate,  $eventLoop)  {      $saucePan  =  new  AsynchronousPan($eventLoop);      $saucePan-­‐>fill(new  OliveOil());      echo  "saucePan:  L'huile  chauffe\n";      $saucePan-­‐>warm($duration  =  2,  function()  use($saucePan,  $plate)  {          echo  "saucePan:  L'huile  est  chaude\n";          $saucePan-­‐>fill(MirepoixFactory::create($withGarlic  =  true));          echo  "saucePan:  Lancement  de  la  cuisson  du  mirepoix\n";          $saucePan-­‐>warm($duration  =  5,  function()  use($saucePan,  $plate)  {              echo  "saucePan:  Le  mirepoix  est  prêt  pour  la  tomate\n";              $saucePan-­‐>fill(TomatoFactory::create());              echo  "saucePan:  Lancement  de  la  cuisson  de  la  tomate\n";              $saucePan-­‐>warm($duration  =  4,  function()  use($saucePan,  $plate)  {                  echo  "saucePan:  La  sauce  est  prête\n";                  $plate-­‐>addContentsOf($saucePan);              });          });      });  });  !$eventLoop-­‐>start();  $plate-­‐>serve('Régalez-­‐vous');

SÉQUENTIALITÉSy

nchro

ne

Asynchrone

pastaPan:  Allumage  tic-­‐tac  :  1  ...  tic-­‐tac  :  7  saucePan:  L'huile  chauffe  tic-­‐tac  :  8  tic-­‐tac  :  9  saucePan:  L'huile  est  chaude  saucePan:  Lancement  de  la  cuisson  du  mirepoix  tic-­‐tac  :  10  pastaPan:  L'eau  bout  pastaPan:  Lancement  de  la  cuisson  des  spaghettis  tic-­‐tac  :  11  ....  tic-­‐tac  :  14  saucePan:  Le  mirepoix  est  prêt  pour  la  tomate  saucePan:  Lancement  de  la  cuisson  de  la  tomate  tic-­‐tac  :  15  ...  tic-­‐tac  :  18  pastaPan:  Les  spaghettis  sont  prêts  saucePan:  La  sauce  est  prête  Régalez-­‐vous

SCRIPT SYNCHRONE

29 MINUTES

SCRIPT ASYNCHRONE

18 MINUTES

7/10 NOTE DES INVITÉS

class  EventLoop  {      protected  $tick  =  0;      protected  $callbacksForTick  =  array();  !    public  function  executeLater($delay,  $callback)  {          $this-­‐>callbacksForTick[$this-­‐>tick  +  $delay]  []=  $callback;      }          public  function  start()  {          while  ($this-­‐>callbacksForTick)  {              $this-­‐>tick++;              $this-­‐>executeCallbacks();          }      }            public  function  executeCallbacks()  {          echo  "tic-­‐tac  :  "  .  $this-­‐>tick  .  "\n";          if  (!isset($this-­‐>callbacksForTick[$this-­‐>tick]))  {              return;  //  no  callback  to  execute          }          foreach  ($this-­‐>callbacksForTick[$this-­‐>tick]  as  $callback)  {              call_user_func($callback);          }          //  clean  up          unset($this-­‐>callbacksForTick[$this-­‐>tick]);      }  }

//  ...  $eventLoop-­‐>start();  !

//  Never  executed  $plate-­‐>serve('Régalez-­‐vous');

RESYNCHRONISER L’ASYNCHRONE

class  PlateOfSpaghettiWithSauce  extends  Plate  {      protected  $hasSpaghetti  =  false;      protected  $hasSauce  =  false;            public  function  addContentsOf(Pan  $pan)      {          parent::addContentsOf($pan);          if  ($pan-­‐>contains('Spaghetti'))  {              $this-­‐>hasSpaghetti  =  true;          }          if  ($pan-­‐>contains('Tomato'))  {              $this-­‐>hasSauce  =  true;          }          if  ($this-­‐>hasSpaghetti  &&  $this-­‐>hasSauce)  {              $this-­‐>serve('Régalez-­‐vous');          }      }  }

../..  //  sauce  $eventLoop-­‐>executeLater($delay  =  7,  function()  use  ($plate,  $eventLoop)  {      $saucePan  =  new  AsynchronousPan($eventLoop);      $saucePan-­‐>fill(new  OliveOil());      echo  "saucePan:  L'huile  chauffe\n";      $saucePan-­‐>warm($duration  =  2,  function()  use($saucePan,  $plate)  {          echo  "saucePan:  L'huile  est  chaude\n";          $saucePan-­‐>fill(MirepoixFactory::create($withGarlic  =  true));          echo  "saucePan:  Lancement  de  la  cuisson  du  mirepoix\n";          $saucePan-­‐>warm($duration  =  5,  function()  use($saucePan,  $plate)  {              echo  "saucePan:  Le  mirepoix  est  prêt  pour  la  tomate\n";              $saucePan-­‐>fill(TomatoFactory::create());              echo  "saucePan:  Lancement  de  la  cuisson  de  la  tomate\n";              $saucePan-­‐>warm($duration  =  4,  function()  use($saucePan,  $plate)  {                  echo  "saucePan:  La  sauce  est  prête\n";                  $plate-­‐>addContentsOf($saucePan);              });          });      });  });  !$eventLoop-­‐>start();  $plate-­‐>serve('Régalez-­‐vous');

DÉMÊLER LE CODE

SPAGHETTI

$saucePan  =  new  AsynchronousPan($eventLoop);  $eventLoop-­‐>executeLater($delay  =  7,  function()  {      call_user_func($warmSaucePan);  });  $warmSaucePan  =  function()  use  ($saucePan)  {      $saucePan-­‐>fill(new  OliveOil());      echo  "saucePan:  L'huile  chauffe\n";      $saucePan-­‐>warm($duration  =  2,  $cookMirepoix);  };  $cookMirepoix  =  function()  use  ($saucePan)  {      echo  "saucePan:  L'huile  est  chaude\n";      $saucePan-­‐>fill(MirepoixFactory::create($withGarlic  =  true));      echo  "saucePan:  Lancement  de  la  cuisson  du  mirepoix\n";      $saucePan-­‐>warm($duration  =  5,  $cookTomato);  };  $cookTomato  =  function()  use  ($saucePan)  {      echo  "saucePan:  Le  mirepoix  est  prêt  pour  la  tomate\n";      $saucePan-­‐>fill(TomatoFactory::create());      echo  "saucePan:  Lancement  de  la  cuisson  de  la  tomate\n";      $saucePan-­‐>warm($duration  =  4,  $serveSauce);  };  $serveSauce  =  function()  use  ($saucePan,  $plate)  {      echo  "saucePan:  La  sauce  est  prête\n";      $plate-­‐>addContentsOf($saucePan);  };

$saucePan  =  new  AsynchronousPan($eventLoop);  $serveSauce  =  function()  use  ($saucePan,  $plate)  {      echo  "saucePan:  La  sauce  est  prête\n";      $plate-­‐>addContentsOf($saucePan);  };  $cookTomato  =  function()  use  ($saucePan,  $serveSauce)  {      echo  "saucePan:  Le  mirepoix  est  prêt  pour  la  tomate\n";      $saucePan-­‐>fill(TomatoFactory::create());      echo  "saucePan:  Lancement  de  la  cuisson  de  la  tomate\n";      $saucePan-­‐>warm($duration  =  4,  $serveSauce);  };  $cookMirepoix  =  function()  use  ($saucePan,  $cookTomato)  {      echo  "saucePan:  L'huile  est  chaude\n";      $saucePan-­‐>fill(MirepoixFactory::create($withGarlic  =  true));      echo  "saucePan:  Lancement  de  la  cuisson  du  mirepoix\n";      $saucePan-­‐>warm($duration  =  5,  $cookTomato);  };  $warmSaucePan  =  function()  use  ($saucePan,  $cookMirepoix)  {      $saucePan-­‐>fill(new  OliveOil());      echo  "saucePan:  L'huile  chauffe\n";      $saucePan-­‐>warm($duration  =  2,  $cookMirepoix);  };  $eventLoop-­‐>executeLater($delay  =  7,  function()  use  ($warmSaucePan)  {          call_user_func($warmSaucePan);  });

$saucePan  =  new  AsynchronousPan($eventLoop);  $warmSaucePan  =  function($callback)  use  ($saucePan)  {      $saucePan-­‐>fill(new  OliveOil());      echo  "saucePan:  L'huile  chauffe\n";      $saucePan-­‐>warm($duration  =  2,  $callback);  };  $cookMirepoix  =  function($callback)  use  ($saucePan)  {      echo  "saucePan:  L'huile  est  chaude\n";      $saucePan-­‐>fill(MirepoixFactory::create($withGarlic  =  true));      echo  "saucePan:  Lancement  de  la  cuisson  du  mirepoix\n";      $saucePan-­‐>warm($duration  =  5,  $callback);  };  $cookTomato  =  function($callback)  use  ($saucePan)  {      echo  "saucePan:  Le  mirepoix  est  prêt  pour  la  tomate\n";      $saucePan-­‐>fill(TomatoFactory::create());      echo  "saucePan:  Lancement  de  la  cuisson  de  la  tomate\n";      $saucePan-­‐>warm($duration  =  4,  $callback);  };  $serveSauce  =  function()  use  ($saucePan,  $plate)  {      echo  "saucePan:  La  sauce  est  prête\n";      $plate-­‐>addContentsOf($saucePan);  };  $eventLoop-­‐>executeLater($delay  =  7,  function()  use  ($plate,  $eventLoop)  {      Async::waterfall(          array($warmSaucePan,  $cookMirepoix,  $cookTomato),          $serveSauce      );  });

class  Async  {      public  static  function  waterfall($tasks,  $callback  =  null)      {              $taskCallback  =  function  ()  use  (&$next)  {                      call_user_func_array($next,  func_get_args());              };              $done  =  function  ()  use  ($callback)  {                      if  ($callback)  {                              call_user_func_array($callback,  func_get_args());                      }              };              $next  =  function  ()  use  (&$tasks,  $taskCallback,  $done)  {                      if  (0  ===  count($tasks))  {                              call_user_func_array($done,  func_get_args());                              return;                      }  !                    $task  =  array_shift($tasks);                      $args  =  array_merge(func_get_args(),  array($taskCallback));                      call_user_func_array($task,  $args);              };              $next();      }  }

Source: https://github.com/reactphp/async/blob/master/src/React/Async/Util.php#L81

EN ASYNCHRONE PAS DE RETURN

//  prototype  synchronous  function  $cook  =  function($ingredient)  use  ($saucePan)  {      $saucePan-­‐>fill($ingredient);      $saucePan-­‐>warm(5);      return  'chaud  devant';  };  !

!

$message  =  $cook(MirepoixFactory::create());  echo  $message,  "\n";  

//  prototype  asynchronous  function  $cook  =  function($ingredient,  $callback)  use  ($saucePan)  {      $saucePan-­‐>fill($ingredient);      $saucePan-­‐>warm(5,  function()  {          $callback('chaud  devant');      });  };  !

$cook(MirepoixFactory::create(),  function($message)  {      echo  $message,  "\n";  });

EN ASYNCHRONE PAS DE TRY/CATCH

$cook  =  function($ingredient,  $callback)  use  ($saucePan)  {      $saucePan-­‐>fill($ingredient);      $saucePan-­‐>warm(5,  function()  {          $isSummer  =  in_array(date('m'),    array(6,  7,  8)):          if  ($saucePan-­‐>contains('Tomato')  &&  !$isSummer)  {              throw  new  OutOfBoundsException('On  ne  fait                    de  la  bonne  sauce  tomate  qu\'en  été');          }          $callback('chaud  devant  !');      });  };  !

try  {      $cook(MirepoixFactory::create(),  function($message)  {          echo  $message,  "\n";      });    }  catch  (OutOfBoundsException  $e)  {      echo  "Echec  de  la  recette\n";  }

$cook  =  function($ingredient,  $callback)  use  ($saucePan)  {      $saucePan-­‐>fill($ingredient);      $saucePan-­‐>warm(5,  function()  {          $isSummer  =  in_array(date('m'),    array(6,  7,  8)):          if  ($saucePan-­‐>contains('Tomato')  &&  !$isSummer)  {              return  callback(new  OutOfBoundsException('On  ne  fait                    de  la  bonne  sauce  tomate  qu\'en  ete'));          }          $callback(null,  'chaud  devant  !');      });  };  !

$cook(MirepoixFactory::create(),  function($error,  $message)  {      if  ($error  instanceOf  OutOfBoundsException)  {          echo  "Echec  de  la  recette\n";          return;      }      echo  $message,  "\n";  });

DU CODE RÉUTILISABLE

ROBUSTE UN PLAT

CHAUD

VICTOIRE !

10/10 NOTE DES INVITÉS

IL N’Y A TOUJOURS

QU’UN SEUL CHEF

PROGRAMMATION NON

PARALLELE

PAS DE TEMPS PERDU A ATTENDRE LA RÉPONSE D’UN AUTRE

Operation                          CPU  cycles  L1  ..................  3  L2  ..................  14  RAM  .................  250  Disk  ................  41,000,000  Network  .............  240,000,000

LATENCE I/O

L1  cache  reference  .........................  0.5  ns  L2  cache  reference  ...........................  7  ns  Main  memory  reference  ......................  100  ns                            Compress  1K  bytes  with  Zippy  .............  3,000  ns    =      3  µs  Send  2K  bytes  over  1  Gbps  network  .......  20,000  ns    =    20  µs  SSD  random  read  ........................  150,000  ns    =  150  µs  Read  1  MB  sequentially  from  memory  .....  250,000  ns    =  250  µs  Round  trip  within  same  datacenter  ......  500,000  ns    =  0.5  ms  Read  1  MB  sequentially  from  SSD*  .....  1,000,000  ns    =      1  ms  Disk  seek  ...........................  10,000,000  ns    =    10  ms  Read  1  MB  sequentially  from  disk  ....  20,000,000  ns    =    20  ms  Send  packet  CA-­‐>Netherlands-­‐>CA  ....  150,000,000  ns    =  150  ms

Source: http://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf

I/O = ATTENTE ETABLISSEMENT CONNEXION SÉCURISÉE

RÉCEPTION REQUÊTE HTTP REQUÊTE BASE DE DONNÉES

LECTURE VALEUR DANS MEMCACHE LECTURE DE FICHIER SUR DISQUE

APPEL À UNE API REST ENVOI MESSAGE À UN AMQP

ENVOI RÉPONSE HTTP

90% DU TEMPS DE RÉPONSE

D’UNE REQUÊTE HTTP EST PASSÉ À ATTENDRE UNE I/O

GET  /favicon.ico

Routing Lancement de l’ordre de chargement du fichier

Déplacement de la tête de lecture Transfert des données du disque en mémoire

Envoi de la réponse HTTPTraitement

Attente Fin de la requête

Construction de la réponse HTTP

Process serveur

UN SERVEUR WEB PASSE SON TEMPS A SE TOURNER

LES POUCES

POUR MIEUX UTILISER LE CPU

ON MULTIPLIE LES PROCESS LES THREADS

ET DONC LA CONSO MÉMOIRE

MaxClients  50

UN AUTRE MONDE

EST POSSIBLE

GET  /favicon.ico

Traitement

Attente

Process serveur

I/O disque asynchrone

Transfert des données du disque en mémoire

Envoi de la réponse HTTP

I/O réseau asynchrone

LIBEL EVENT LOOP

LIBEIO ASYNCHRONOUS I/O

LIBUV MULTI-PLATFORM ABSTRACTION LAYER

GET  /favicon.icoGET  /js/jquery.jsGET  /css/main.css

COMMENT FAIRE DES I/O ASYNCHRONES

EN PHP ?

Event-­‐driven,  non-­‐blocking  I/O  with  PHP.  

PHP & PROCESS PERSISTENTS

PAS BON MÉNAGE

PHP N’A PAS DE FONCTIONS D’I/O DISQUE ASYNCHRONES

PECL/LIBIO

TANT QU’À INSTALLER UN BINAIRE

AUTANT EN PRENDRE UN

STABLE POPULAIRE

TANT QU’À REPOSER SUR UNE EVENT LOOP

AUTANT UTILISER UN LANGAGE

ÉVÈNEMENTIEL

TANT QU’À ABUSER DES FONCTIONS ANONYMES

AUTANT UTILISER UN LANGAGE

FONCTIONNEL

var  fs  =  require('fs');  fs.unlink('/tmp/hello',  function  (err)  {      if  (err)  throw  err;      console.log('successfully  deleted  /tmp/hello');  });  //  more  code  console.log('deletion  script');

plate  =  new  Plate();  pastaPan  =  new  AsynchronousPan(eventLoop);  water  =  new  Water();  pastaPan.fill(water);  console.log('pastaPan:  Starting  to  boil  water');  pastaPan.warm(duration  =  10,  function()  {      console.log('pastaPan:  Water  is  boiling');      pastaPan.fill(new  Spaghetti());      console.log('pastaPan:  Starting  to  boil  spaghetti');      pastaPan.warm(duration  =  8,  function()  {          console.log('pastaPan:  Spaghetti  is  ready');          pastaPan.remove(water);          plate.addContentsOf(pastaPan);      });  });

$plate  =  new  Plate();  $pastaPan  =  new  AsynchronousPan($eventLoop);  $water  =  new  Water();  $pastaPan-­‐>fill($water);  echo  "pastaPan:  Starting  to  boil  water\n";  $pastaPan-­‐>warm($duration  =  10,  function()  use  ($pastaPan,  $plate,  $water)  {      echo  "pastaPan:  Water  is  boiling\n";      $pastaPan-­‐>fill(new  Spaghetti());      echo  "pastaPan:  Starting  to  boil  spaghetti\n";      $pastaPan-­‐>warm($duration  =  8,  function()  use  ($pastaPan,  $plate,  $water)  {          echo  "pastaPan:  Spaghetti  is  ready\n";          $pastaPan-­‐>remove($water);          $plate-­‐>addContentsOf($pastaPan);      });  });

I/O ASYNCHRONES UN SEUL CHEF

DES CALLBACKS PLUS DE CONCURRENCE

MOINS DE CONSOL MÉMOIRE PAS POSSIBLE EN PHP, NATIF EN NODE.JS

PAS DE RETURN, PAS DE TRY/CATCH

DES PÂTES CHAUDES

MERCI

François Zaninotto - @francoisz - marmelab.com github.com/fzaninotto - github.com/marmelab