begin process at 2010 03 21 07:10:07
  Trouver un code source :
 
dans
 
Accueil > 

Code

 > 

Class et Objet ( POO )

 > [PHP5] MULTI-THREADING : ACCÉLÉRATION DU TEMPS DE TRAVAIL D'UN SCRIPT.

[PHP5] MULTI-THREADING : ACCÉLÉRATION DU TEMPS DE TRAVAIL D'UN SCRIPT.


 Information sur la source

Note :
8,5 / 10 - par 2 personnes
8,50 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10
Catégorie :Class et Objet ( POO ) Classé sous :thread, process, cli, pcntl, fork Niveau :Débutant Date de création :25/01/2009 Vu :3 216

Auteur : codefalse

Ecrire un message privé
Site perso
Commentaire sur cette source (7)
Ajouter un commentaire et/ou une note

 Description

L'article rédigé par Martin Roest (http://www.ibuildings.com/blog/archives/1539-Boos t-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

  • <?php
  • /**
  • * @class ProcessManager
  • * Class that handle creating multiple process
  • *
  • * @licence GNU/GPLv3
  • *
  • * @author Cyril Nicodème
  • *
  • *
  • * @note : This doesn't work on Windows machine
  • * @note : It is recommended to use this class in an cli environnement,
  • * forking is NOT recommended running from an Apache (or some other preforking web server) module
  • *
  • * @see : http://www.ibuildings.com/blog/archives/1539-Boost-performance-with-parallel-processing.html
  • */
  • 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);
  • }
  • }
  • ?>
<?php
/**
 * @class ProcessManager
 * Class that handle creating multiple process
 * 
 * @licence GNU/GPLv3
 * 
 * @author Cyril Nicodème
 * 
 * 
 * @note : This doesn't work on Windows machine
 * @note : It is recommended to use this class in an cli environnement, 
 * 			forking is NOT recommended running from an Apache (or some other preforking web server) module
 * 
 * @see : http://www.ibuildings.com/blog/archives/1539-Boost-performance-with-parallel-processing.html
 */
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 ? :)


 Sources du même auteur

Source avec Zip GESTIONNAIRE DE MODÈLES SQL
Source avec Zip [PHP5] - SIMPLE CLASSE D'ENVOI D'EMAIL
Source avec Zip CATAPULT FRAMEWORK, VERSION 0.1 : "ORIGIN"
[PHP5] - CLASSE D'UPLOAD
Source avec Zip [PHP5] - CLASSE DE VÉRIFICATION DE FORMULAIRE

 Sources de la même categorie

Source avec Zip CLASSE TABLE HTML DYNAMIQUE par LDDL
Source avec Zip GÉNÉRATEUR DE COUCHE DAO POUR SITE WEB À PARTIR D'UNE BASE D... par alexfool
Source avec Zip [PHP5.2] CLASSE PDO par hornetbzz
Source avec Zip POO - LOGGING PACKAGE par Waredan
POO - OBJECT CLASS par Waredan

 Sources en rapport avec celle ci

Source avec Zip [PHP5] NOTIMEOUT PACKAGE par malalam

Commentaires et avis

Commentaire de coucou747 le 26/01/2009 08:26:07

salut

ce qui est interessant quand on joue avec les multi processus, c'est de faire de la synchronisation (un process attend que l'autre ai finni un truc pour continuer)

on a souvent besoin de ca, exemple simple :

une fonction de confirmation attend que l'utilisateur clique sur une case "ok" ou "cancel" avant de renvoyer vrai ou faux.

pour ca, en java, on a des wait et notify, et c'est super joli, tu gagnes BEAUCOUP de CPU a les utiliser.

Commentaire de codefalse le 26/01/2009 09:51:44 administrateur CS

En effet, c'est mieux qu'une boucle infinie avec un paramètre pour savoir si une autre fonction à répondu :p

Je suis pas très java, malgré que j'aime bien ce langage. Je n'ai pas souvent l'occasion de travailler avec, et je trouve ca dommage :(

Commentaire de hvb le 27/01/2009 18:25:44

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...

Commentaire de coucou747 le 27/01/2009 18:40:20

les deux methodes font de l'execution parallele...

Commentaire de codefalse le 27/01/2009 20:23:22 administrateur CS

@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 :)

Commentaire de masternico le 02/02/2009 06:26:50 8/10

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...

Commentaire de pyrrah le 12/01/2010 11:01:01 9/10

Très utile :)
Merci CodeFalse !

 Ajouter un commentaire


Discussions en rapport avec ce code source dans le forum

De process à service, apache tombe dans les choux [ par msohet ] bonjour les gens et voila mon probleme :j'ai du réalisé une appli en php avec connexion odbc/mysql et tout le bordel que vous connaissez.Pour ce fair De process à service, apache tombe dans les choux [ par msohet ] bonjour les gens et voila mon probleme :j'ai du réalisé une appli en php avec connexion odbc/mysql et tout le bordel que vous connaissez.Pour ce fair CLI / CGI : c koi çà sert à koi et comment on l'utilise ??? [ par PhoenixCB ] Bah oui g entendu parler de çà, et g la version CLI et la version CGI .. mais ... çà sert à quoi ?Et comment on l'utilise ?P.S. : chuis sous PHP 4.3.4 Comment récuperer le résultat d'une requete dans une variable en php? [ par neilero ] SalutJe n'arrive pas à récuperer le résultat d'une requete dans une variable en php.je ne vois pas ce qu'il ne va pas://requete permettant de recupere probleme d'envoi email [ par pietbrouwers ] quelqu'un peut-il me dire ce qui ne fonctionne pas dans le script suivant : je n'arrive pas a recevoir par mail&nbsp; le contenu de la facture. voir l Nouveau site, mais (suite) [ par malalam ] Hello, j'ouvre un nouveau thread pour ce sujet car d'une part l'autre est trop loin, d'autre part je ne le trouve plus malgre mes recherches... Enco Process distant [ par JLN ] Bonjour, &nbsp;&nbsp;&nbsp;&nbsp; Je voudrai connaitre tous les process qui tournent sur ma machine distante et les afficher avec ce systeme, as-tu un affichage de champs [ par chris tuckers ] Bonjour, alors, c'est une question top urgente et je bloque dessus depuis ce matin. Dans une page, je d&#233;sirais voir renseigner deux tables. Voici un else qui en renvoit pas ce que je veux [ par oceane751 ] bonjour &#224; tous! et joyeuses paques!voil&#224; mon soucissi un num&#233;ro de client n'existe pas, un message informe la personne qui utiilise l'a session_register en PHP CLI [ par motherboy ] Bonjour à tous,Je développe actuellement un Bot IRC, linké à un IRCd Unreal, et ayant des fonctions de Anope.Donc je cherche à savoir si je peux mémor


Nos sponsors


Sondage...

Comparez les prix


HTC Hero

Entre 550€ et 550€

CalendriCode

Mars 2010
LMMJVSD
1234567
891011121314
15161718192021
22232425262728
293031    

Consulter la suite du CalendriCode

 
Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils.
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés

Google Coop CodeS-SourceS Google Coop CodeS-SourceS
Temps d'éxécution de la page : 0,718 sec (3)

Nous contacter | Annoncer sur CodeS-SourceS | Mentions légales