Maison  >  Article  >  développement back-end  >  Méthode d'implémentation du minuteur PHP multitâche de deuxième niveau

Méthode d'implémentation du minuteur PHP multitâche de deuxième niveau

藏色散人
藏色散人avant
2020-01-15 17:46:552537parcourir

Description

Lorsque je déployais récemment crontab dans mon entreprise, je me suis soudainement demandé si je pouvais utiliser PHP pour implémenter un minuteur avec une granularité allant jusqu'à quelques secondes, car crontab ne peut monter que en minutes. En même temps, j'ai également recherché qu'il n'y avait pas beaucoup de timers implémentés en PHP. Swoole a été étendu pour implémenter un timer au niveau de la milliseconde, ce qui est très efficace, mais après tout, il n'est pas écrit en code PHP pur. , donc à la fin, j'ai toujours envisagé d'utiliser PHP pour implémenter une classe timer à des fins d'apprentissage.

Implémentation

Lors de l'implémentation du code du timer, deux extensions fournies avec le système PHP sont utilisées

Pcntl - extension multi-processus :

L'objectif principal est de permettre à PHP d'ouvrir de nombreux sous-processus en même temps et de traiter certaines tâches en parallèle.

Spl - SplMinHeap - Petit tas supérieur

Une petite structure de données en tas supérieur Lors de l'implémentation d'un minuteur, l'utilisation de cette structure est très efficace. est O (logN). Les minuteries comme Libevent ont également utilisé rbtree avant d'adopter cette structure de données après la version 1.4. Si une liste chaînée ou un tableau fixe est utilisé, chaque insertion ou suppression devra peut-être être parcourue ou triée à nouveau. .

Processus

Méthode dimplémentation du minuteur PHP multitâche de deuxième niveau

Explication

Définir la structure de la minuterie, qu'est-ce que c'est. là Paramètres et autres.

2. Ensuite, enregistrez-les tous dans notre classe timer Timer.

3. Appelez la méthode de surveillance de la classe timer et démarrez la surveillance.

4. Le processus de surveillance est une boucle while sans fin, vérifiant constamment si le haut du tas de temps a expiré. J'avais initialement envisagé de boucler pour vérifier une fois par seconde, mais j'ai ensuite pensé que ce serait toujours un problème de boucler pour vérifier une fois par seconde. . Si cela se produit pendant notre sommeil (1) Lorsque le minuteur expire, nous ne pouvons pas l'exécuter avec précision immédiatement, et il peut y avoir un risque de retard, nous utilisons donc toujours usleep(1000) pour le voir en millisecondes et suspendre le processus. pour réduire la charge du processeur.

Code

/***
* Class Timer
*/
class Timer extends SplMinHeap
{
  /**
  * 比较根节点和新插入节点大小
  * @param mixed $value1
  * @param mixed $value2
  * @return int
  */
  protected function compare($value1, $value2)
  {
    if ($value1['timeout'] > $value2['timeout']) {
      return -1;
    }
    if ($value1[&#39;timeout&#39;] < $value2[&#39;timeout&#39;]) {
      return 1;
    }
    return 0;
  }
  /**
  * 插入节点
  * @param mixed $value
  */
  public function insert($value)
  {
    $value[&#39;timeout&#39;] = time() + $value[&#39;expire&#39;];
    parent::insert($value);
  }
  /**
  * 监听
  * @param bool $debug
  */
  public function monitor($debug = false)
  {
    while (!$this->isEmpty()) {
      $this->exec($debug);
      usleep(1000);
    }
  }
  /**
  * 执行
  * @param $debug
  */
  private function exec($debug)
  {
    $hit = 0;
    $t1  = microtime(true);
    while (!$this->isEmpty()) {
      $node = $this->top();
      if ($node[&#39;timeout&#39;] <= time()) {
        //出堆或入堆
        $node[&#39;repeat&#39;] ? $this->insert($this->extract()) : $this->extract();
        $hit = 1;
        //开启子进程
        if (pcntl_fork() == 0) {
          empty($node[&#39;action&#39;]) ? &#39;&#39; : call_user_func($node[&#39;action&#39;]);
          exit(0);
        }
        //忽略子进程,子进程退出由系统回收
        pcntl_signal(SIGCLD, SIG_IGN);
      } else {
        break;
      }
    }
    $t2 = microtime(true);
    echo ($debug && $hit) ? &#39;时间堆 - 调整耗时: &#39; . round($t2 - $t1, 3) . "秒\r\n" : &#39;&#39;;
  }
}

Instance

$timer = new Timer();
//注册 - 3s - 重复触发
$timer->insert(array(&#39;expire&#39; => 3, &#39;repeat&#39; => true, &#39;action&#39; => function(){
  echo &#39;3秒 - 重复 - hello world&#39; . "\r\n";
}));
//注册 - 3s - 重复触发
$timer->insert(array(&#39;expire&#39; => 3, &#39;repeat&#39; => true, &#39;action&#39; => function(){
  echo &#39;3秒 - 重复 - gogo&#39; . "\r\n";
}));
//注册 - 6s - 触发一次
$timer->insert(array(&#39;expire&#39; => 6, &#39;repeat&#39; => false, &#39;action&#39; => function(){
  echo &#39;6秒 - 一次 - hello xxxx&#39; . "\r\n";
}));
//监听
$timer->monitor(false);

Résultat de l'exécution

Méthode dimplémentation du minuteur PHP multitâche de deuxième niveau

J'ai également testé une situation extrême. En même temps, 1000 minuteries expirent toutes en 1 seconde, et il ne faut que 0,126 s pour ajuster toutes les piles de temps. Ce n'est pas un problème, mais à chaque fois. la minuterie est ajustée, un processus enfant doit être démarré, cela peut prendre beaucoup de temps. Il est possible que les 1 000 éléments ne puissent pas être traités en 1 seconde, ce qui affectera le déclenchement continu de la surveillance suivante. Le processus enfant n'est pas démarré, par exemple, il peut toujours être traité par exécution directe. . . . Bien sûr, il doit y avoir une meilleure solution, mais c'est la seule chose à laquelle je pense pour le moment.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer