[php5] multi-threading : accélération du temps de travail d'un script.

Soyez le premier à donner votre avis sur cette source.

Snippet vu 8 023 fois - Téléchargée 18 fois

Contenu du snippet

L'article rédigé par Martin Roest (http://www.ibuildings.com/blog/archives/1539-Boost-performance-with-parallel-processing.html) nous montre à quel point il peut-être facile de jouer avec plusieurs processus en même temps, même en PHP.

J'ai personnellement été très séduit par la simplicité, mais je trouve que l'on pouvais encore faire plus simple : une petite classe :)

L'utilité d'un tel code se trouve sur des scripts de traitement, tel qu'un remaniement de base de donnée, une modifications sur plusieurs fichiers, etc. Par contre, il est important de savoir que la gestion des processus tel qu'il est utilisé ici ne fonctionne pas sous Windows, et qu'il n'est pas conseillé d'appeler ce script depuis un environnement type Apache.

En gros et pour faire simple, ce type de script s'utilise en ligne de commande (php-cli) et dans un environnement Unix, type cron.

Pour vous prouver l'efficacité de mes dires, voici un code bateau qui utilise 5 processus simultanés pour exécuter un script :

Source / Exemple :


<?php
/**


class ProcessManager {
	/**

  • Contain the Processus Id of the current processus
  • @var Integer $_iPid
  • /
private $_iPid; /**
  • Contain the priority for the current processus
  • @var Integer $_iPriority
  • /
private $_iPriority = 0; /**
  • Contain a list of all the childrens
  • (in case the current processus is the father)
  • @var Array $_aChildrens
  • /
private $_aChildrens = array (); /**
  • Contain the number of max allowed childrens
  • @var Integer $_iMaxChildrens
  • /
private $_iMaxChildrens = 2; /**
  • Constructor
  • Test if this application can be used, set the MaxChildren value,
  • retrieve his Process ID and set the signals
  • @param Integer $iMaxChildrens (optional)
  • @return ProcessManager
  • /
public function __construct ($iMaxChildrens = 2) { if (!function_exists ('pcntl_fork')) throw new Exception ('Your configuration does not include pcntl functions.'); if (!is_int ($iMaxChildrens) || $iMaxChildrens < 1) throw new Exception ('Childrens must be an Integer'); $this->_iMaxChildrens = $iMaxChildrens; $this->_iPid = getmypid (); // Setting up the signal handlers $this->addSignal (SIGTERM, array ($this, 'signalHandler')); $this->addSignal (SIGQUIT, array ($this, 'signalHandler')); $this->addSignal (SIGINT, array ($this, 'signalHandler')); } public function __destruct () { foreach ($this->_aChildrens as $iChildrensPid) pcntl_waitpid ($iChildrensPid, $iStatus); } /**
  • Fork a Processus
  • @return void
  • /
public function fork ($mFunction, $aParams = array ()) { if (!is_string ($mFunction) && !is_array ($mFunction)) throw new Exception ('Function given must be a String or an Array'); if (!is_array ($aParams)) throw new Exception ('Parameters must be an Array'); $iPid = pcntl_fork (); if ($iPid === -1) throw new Exception ('Unable to fork.'); elseif ($iPid > 0) { // We are in the parent process $this->_aChildrens[] = $iPid; if (count ($this->_aChildrens) >= $this->_iMaxChildrens) { pcntl_waitpid (array_shift ($this->_aChildrens), $iStatus); } } elseif ($iPid === 0) { // We are in the child process call_user_func_array ($mFunction, $aParams); exit (0); } } /**
  • Add a new signal that will be called to the given function with some optionnals parameters
  • @param Integer $iSignal
  • @param Mixed $mFunction
  • @param Array $aParams[optional]
  • @return void
  • /
public function addSignal ($iSignal, $mFunction) { if (!is_int ($iSignal)) throw new Exception ('Signal must be an Integer.'); if (!is_string ($mFunction) && !is_array ($mFunction)) throw new Exception ('Function to callback must be a String or an Array.'); if (!pcntl_signal ($iSignal, $mFunction)) throw new Exception ('Unable to set up the signal.'); } /**
  • The default signal handler, to avoid Zombies
  • @param Integer $iSignal
  • @return void
  • /
public function signalHandler ($iSignal = SIGTERM) { switch ($iSignal) { case SIGTERM: // Finish exit (0); break; case SIGQUIT: // Quit case SIGINT: // Stop from the keyboard case SIGKILL: // Kill exit (1); break; } } /**
  • Set the number of max childrens
  • @param Integer $iMaxChildren
  • @return void
  • /
public function setMaxChildren ($iMaxChildren) { if (!is_int ($iMaxChildrens) || $iMaxChildrens < 1) throw new Exception ('Childrens must be an Integer'); $this->_iMaxChildrens = $iMaxChildrens; } /**
  • Return the current number of MaxChildrens
  • @return Integer
  • /
public function getMaxChildrens () { return self::$_iMaxChildrens; } /**
  • Set the priority of the current processus.
  • @param Integer $iPriority
  • @param Integer $iProcessIdentifier[optional]
  • @return void
  • /
public function setPriority ($iPriority, $iProcessIdentifier = PRIO_PROCESS) { if (!is_int ($iPriority) || $iPriority < -20 || $iPriority > 20) throw new Exception ('Invalid priority.'); if ($iProcessIdentifier != PRIO_PROCESS || $iProcessIdentifier != PRIO_PGRP || $iProcessIdentifier != PRIO_USER) throw new Exception ('Invalid Process Identifier type.'); if (!pcntl_setpriority ($iPriority, $this->_iPid, $iProcessIdentifier)) throw new Exception ('Unable to set the priority.'); self::$_iPriority = $iPriority; } /**
  • Get the priority of the current processus.
  • @return Integer
  • /
public function getPriority () { return self::$_iPriority; } /**
  • Return the PID of the current process
  • @return Integer
  • /
public function getMyPid () { return $this->_iPid; } } ?> <?php ///////////////////////////////////////////////////////////////////////////////////////////// ?> <?php /**
  • Voici un exemple de ce que cela pourrait donner :
  • Dans un terminal sous linux, appelez le, et vous devriez voir la façon dont la méthode "doBigWork" est appelée (5 fois par 5 fois jusqu'à 12) (5, 5, 2)
  • /
require_once ('ProcessManager.php'); function doBigWork ($iWork) { echo 'Sleeping for Work N° '.$iWork."\n"; sleep (20); } try { // We instanciate the ProcessManager with 5 childs $oPM = new ProcessManager (5); } catch (Exception $oE) { die ('Your configuration does not support "pcntl" methods.'); } for ($i = 0; $i < 12; $i++) { // It could happen that the script couldn't fork a process. In that case, an Exception would be raised try { $oPM->fork ('doBigWork', array ($i)); } catch (Exception $oE) { echo 'Using non forked way :'."\n"; doBigWork ($i); } } ?>

Conclusion :


Au final, sans utiliser plusieurs processus, ce code aurait pris 12*20 = 240 secondes.
Avec 5 enfants, le temps de travail est divisé par ... 5, soit 48 secondes ! Quand même !

Bien entendu, vous pouvez augmenter le nombre d'enfant, tout dépendra des ressources que consomment votre fonction de travail (histoire de ne pas tuer votre machine (je l'ai fait pendant les tests :p)).

Une dernière modification qui serait sympathique, c'est d'inclure les fonctions lambdas dans la méthode fork, au lieu de l'appel à une méthode en utilisant le call_user_func_array. Mais ma configuration actuelle de Php n'est pas encore en 5.3, donc je ne peux ni jouer avec les closures, ni avec les fonctions lambdas :p Peut-être plus tard ? :)

A voir également

Ajouter un commentaire

Commentaires

Messages postés
129
Date d'inscription
mercredi 16 février 2005
Statut
Membre
Dernière intervention
15 février 2010
4
Très utile :)
Merci CodeFalse !
Messages postés
487
Date d'inscription
dimanche 5 octobre 2003
Statut
Membre
Dernière intervention
1 septembre 2011

Salut,
Je ne savais pas que l'on pouvait forker en PHP.
Cela vient de me donner des idées nouvelles pour un projet qui était sommeil depuis un petit moment.

Pour ce qui est de la différence entre multi-process et multi-thread, je dirais que le premier donne vie à des processus enfant indépendant les un des autres dans le sens où si l'on déclare un tableau de 2000 éléments servant de zone de swap, chacun des enfants aura son propre swap, alors qu'avec le multi-thread, tout le monde utilise la même zone de swap. Il faut donc avoir un processus maitre qui va gérer les accès à la zone en évitant que deux thread ne tentent d'écrire en même temps au même endroit.

Pour faire du multi-thread, il faut avoir un processus central qui va répartir les temps d'execution de ses enfants ainsi que leurs ressources. Dans le cas présent, on créer des enfants mais on laisse le moteur php de la machine en gérer l'exécution. Donc c'est php qui est multi-thread, alors que ton source lui est multi-process...
Messages postés
1123
Date d'inscription
mardi 8 janvier 2002
Statut
Modérateur
Dernière intervention
21 avril 2009
1
@HVB: d'après ce que tu dit, ce serait donc du multi-threading, car mes deux threads/processus "partagent les mêmes données", non ?

Il est possible que je fasse le mélange entre les deux, donc un peu d'éclairages sont les bienvenus dans ce cas :)
Messages postés
12303
Date d'inscription
mardi 10 février 2004
Statut
Modérateur
Dernière intervention
30 juillet 2012
36
les deux methodes font de l'execution parallele...
Messages postés
939
Date d'inscription
vendredi 25 octobre 2002
Statut
Membre
Dernière intervention
27 janvier 2009
1
J'ai pas lu le code, mais au vu de la description, et de ce que je sais du PHP... ceci n'a rien du concept de multi-threading. Forker un process ne veux pas dire multi-threading.
Tu fais du multi-processus, ce qui est fondamentalement différent... (tu le dis toi-même d'ailleurs, tu parle de processus, et jamais de threads...)

Ce qui m'avait le plus marqué pour bien comprendre la différence théorique :
Deux processus sont indépendant l'un de l'autre.
Deux threads partagent les mêmes données.

C'est super vite résumé parce que j'ai fini ma journée de boulot ( :p ), mais ça résume à peu près la chose...
Afficher les 7 commentaires

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.