Utiliser du multithreading en PHP avec les fonctions PCNTL

Utiliser du multithreading en PHP avec les fonctions PCNTL

Quentin C. 21 April 2012

Aujourd’hui j’ai eu besoin d’effectuer des fonctions faite maison en PHP qui prennent plus de 5 minutes et le tout 50.000 fois ….

Normalement il faut attendre que la première fonction soit terminé pour lancer la deuxième !

Je vais donc vous montrer comment lancer par exemple 20 fois la fonction en même temps :

Tout d’abord il faut que votre serveur ai l’extension pcntl qui permet de jouer avec les processus système :

/scripts/easyapache --enable-pcntl

Une fois Easyapache re-compilée, vous pouvez utiliser des scripts avec pcntl !

Je vais utiliser une classe toute faite “ProcessManager.php”:

_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;
    }
}
?>

Voici mon fichier PHP (Perso)

".$i."
"; sleep(1); } //Calcul du temps $temps_debut = microtime_float(); //Petite boucle 15 fois for ($i=0;$i<=15;$i++) { //Appelle de la fonction "magrosse_fonction" try { $oPM->fork('magrosse_fonction',array($i)); } catch (Exception $oE) { echo 'Using non forked way :'.$oE."
\n"; magrosse_fonction($i); } } //Calcul du temps d'execution $temps_fin = microtime(true); $secondes = round($temps_fin - $temps_debut, 4); echo ("Temps d execution : $secondes seconde(s)
"); ?>

Résultat :

Fonction numéro : 0
Fonction numéro : 1
Fonction numéro : 3
Fonction numéro : 2
Fonction numéro : 4
Fonction numéro : 5
Fonction numéro : 6
Fonction numéro : 7
Fonction numéro : 8
Fonction numéro : 9
Fonction numéro : 10
Fonction numéro : 11
Fonction numéro : 12
Fonction numéro : 13
Fonction numéro : 14
Fonction numéro : 15
Temps d’exécution : 3.036 seconde(s)

10 thoughts on “Utiliser du multithreading en PHP avec les fonctions PCNTL

  1. Quand vous dites re compilé easyapache, il faut lancé en ssh :
    /scripts/easyapache –enable-pcntl pour activer l’option et lancer la re compilation ?
    C’est juste pour la compréhension 🙂

  2. bonjour,
    j’aimerai savoir si c’est possible de lancer des thread sous windows sans apache en passant uniquement par l’interpréteur php.exe en mode cli
    merci

  3. Donc on se connecte en SSH ensuite on lance en ligne de commande :
    sudo /scripts/easyapache –enable-pcntl
    On attend quelques minutes la recompilation ensuite on relance apache ?
    🙂

  4. J’ai fais la recompilation avec la ligne de commande tout c’est bien déroulé sauf que le module n’est toujours pas présent -_-
    J’ai tester avec votre classe qui me retourne:

    Your configuration does not support “pcntl” methods.

    Ai je oublier quelques choses dans les options de compilation tout à l’heure ?

    1. Oui oui, j’ai bien retranscrit correctement votre classe et tester sendui qui justement doit utiliser le module “pcntl”, forcément il ne fonctionne pas, il retourne que pcntl n’est pas disponible.

  5. Bonjour,

    J’essaye de faire un système de gestion de cron selon client(plusieurs client sur la même machine) et priorité

    Et je suis tomber sur votre article fort intéressant. mais par contre je n’ai pas bien compris comment bien l’utiliser.

    Par exemple quand vous dite au constructeur 5 enfants, cela correspond à quoi?
    Et quand vous faites 15 forks avec la boucle, comment cela fonctionne-t-il ? il réparti chaque fork sur les 5 emplacements disponible ou il lance 15 processus ?

    Et peut t-on notifié d’une priorité à certain fork ?
    Peut-on faire varié le nombre d’emplacement disponible ???

    Merci de votre aide

Leave a Reply to Desjardins Jérôme Cancel reply

Your email address will not be published.