PHP fait du multitâche

Pour les besoins d’un projet effectuant un grand nombre de tâches répétitives, j’ai dû mettre en place un système basé sur un script PHP lancé par cron à intervalles réguliers. Il n’y a aucune difficulté particulière à réaliser ceci, là où ça commence à être intéressant c’est à partir du moment où il a fallu que ce script puisse lancer plusieurs tâches en même temps.

On a donc un script lancé à intervalles réguliers qui lui même va lancer et gérer un certain nombre, configurable, de tâches concurrente. Voici comment j’ai procédé.

Avant de continuer, il est bon de noter que j’ai effectué tous mes tests sur une machine fonctionnant sous linux, je n’ai aucune idée de la façon dont ça peu fonctionner sous windows, si jamais ça fonctionne sous windows les fonctions pcntl ne fonctionnent pas sous windows, donc pas de multitâche, avec cette méthode en tous cas, pour cette plateforme.

Un petit exemple vaut mieux que de longs discours :

function task($msg)
{
    echo "Task say $msg\n";
}
 
$t = new Task();
$t->fork('task', array("hello"));

On peut sans problème lancer plusieurs tâches en créant plusieurs objets Task.

Et vérifier qu’une tâche est en cours avec la méthode is_running()

Ici, pour l’exemple, on lance simplement une nouvelle tâche dont l’action principale sera d’afficher du texte, mais en général on à tendance à faire des tâches pour effectuer un traitement lourd ou long. Par exemple si on doit vérifier qu’une liste sites répondent correctement (disons 10 000 sites) sans utiliser de multitâche, si on a un timeout de 5 secondes et que 10% des sites sont hs, on perdra 5000 secondes à attendre. Par contre si on lance 100 tâches, potentiellement, on divises par 100 le temps d’attente.

Voici le code de cette classe

class Task
{
    private $pid;
    private $priority=0;
 
    protected $children_pid=array();
 
    public function __construct()
    {
        if(!function_exists("pcntl_fork"))
        {
            throw new Exception ("Your system does not support pcntl (thread) functions");
        }
 
        // Set the default signal handler
        $this->add_signal(SIGTERM, array($this, "signal_handler"));
        $this->add_signal(SIGINT, array($this, "signal_handler"));
        $this->add_signal(SIGQUIT, array($this, "signal_handler"));
    } 
 
    public function __destruct()
    {
        $this->wait_children();
    } 
 
    public function is_running()
    {
        $pid = pcntl_waitpid($this->pid, $status, WNOHANG);
        return($pid === 0);
    }  
 
    public function wait_children()
    {
        foreach($this->children_pid as $child_pid)
        {
            pcntl_waitpid($child_pid, $status);
        }
    }
 
    public function fork($name, $params=array())
    {
        $pid = pcntl_fork();
 
        if($pid === -1)
        {
            throw new Exception("Unable to fork");
        }
        elseif($pid > 0)
        {
            // Parent
            $this->children_pid[] = $pid;
        }
        elseif($pid === 0)
        {
            // Child
            call_user_func_array($name, $params);
            exit(0);
        }
    }
 
    public function add_signal($signal, $function_name)
    {
        if(!pcntl_signal($signal, $function_name))
        {
            throw new Exception("Can't add the signal");
        }
    }
 
    public function signal_handler($signal)
    {
        switch($signal)
        {
            default:
            case SIGTERM:
                exit(0);
            break;
 
            case SIGQUIT:
            case SIGINT:
            case SIGKILL:
                exit(1);
            break;
        }
    }
 
    public function get_pid()
    {
        return $this->pid;
    }
 
    public function set_priority($priority, $process_identifier=PRIO_PROCESS)
    {
        if(!is_int($priority) || $priority < -20 || $priority > 20)
        {
            throw new Exception("Invalid priority");
        }
 
        if($process_identifier != PRIO_PROCESS || $process_identifier != PRIO_PGRP || $process_identifier != PRIO_USER)
        {
            throw new Exception("Invalid process identifier type");
        }
 
        if(!pcntl_setpriority($priority, $this->pid, $process_identifier))
        {
            throw new Exception("Can't set the priority");
        }
 
        $this->priority = $priority;
    }
 
    public function get_priority()
    {
        return $this->priority;
    }
 
    public function kill($pid)
    {
        posix_kill(pid, SIGHUP);
    }
}

Read 3 comments

  1. J’apporte une petite précision : ce que tu fais n’a rien à voir avec du threading.
    Avec du threading, un processus est capable d’effectuer plusieurs tâches simultanément en interne.
    Ainsi, un thread n’est pas un processus, et il n’apparait pas dans la liste des processus renvoyé par ps, par exemple, puisqu’il n’a pas de PID.
    PHP ne sait pas faire de threading, ce qui est d’ailleur l’une des raison pour laquelle php-gtk n’est pas si intéressant que cela.
    Le multitâche, qui est ce que tu fais, permet quand à lui de faire plusieurs tâches mais via plusieurs processus, qui ont chacun un PID et sont chacun visibles la liste des processus.
    Par ailleurs, un thread est capable de dialoguer avec son processus père de manière interne, alors qu’un procesus « forké » devra le faire via un mécanisme tiers type mémoire partagée, fichier temporaire, etc.
    Les deux notions sont donc fonctionnellement équivalentes mais n’ont pas les même implications au niveau du code et du fonctionnement de l’ensemble, ce qui fait dire que les threads sont plus efficaces que le multitâche.
    Et j’adorerais que PHP ait du threading…

Laisser un commentaire