PHP fait du multitâche (par Stéphane Brun, Consultant indépendant)

Présentation d'une classe PHP conçue pour lancer plusieurs processus. Cette méthode est utile pour des applications effectuant un grand nombre de tâches répétitives.

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 cet OS, donc pas de multitâche, avec cette méthode en tous cas, pour cette plate-forme.

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 hors service, 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);
   }
}  

Article réalisé par Stéphane Brun (Développement Web Libre) sous licence Creative Commons