説明
最近、会社で crontab を導入していたとき、PHP を使用して秒単位のタイマーを実装できないかと突然疑問に思いました。なぜなら、crontab は起動できるまでしか実行できないからです。同時に、PHP で実装されているタイマーが少ないことも調査しました。Swoole はミリ秒レベルのタイマーを実装するように拡張されており、非常に効率的ですが、結局のところ、純粋な PHP コードで書かれていません。ということで、結局PHPを使うことを検討しましたが、学習参考用のタイマークラスを実装することにしました。
実装
タイマー コードを実装するときは、PHP システムに付属の 2 つの拡張機能が使用されます。
Pcntl - マルチプロセス拡張機能:
主な目的は、PHP が多数のサブプロセスを同時に開き、いくつかのタスクを並行して処理できるようにすることです。
Spl - SplMinHeap - スモール トップ ヒープ
スモール トップ ヒープ データ構造。タイマーを実装する場合、この構造を使用すると非常に効率的です。挿入と削除の時間計算量は非常に低くなります。は O(logN) です。バージョン 1.4 以降、このデータ構造を採用する前は、libevent などのタイマーも rbtree を使用していました。リンクされたリストまたは固定配列が使用されている場合、挿入または削除のたびに再度走査またはソートする必要がある場合があります。まだパフォーマンスの問題がいくつかあります。 。
プロセス
説明
1. タイマー構造を定義します。パラメータなどがあります。
2. 次に、それらをすべてタイマー クラス Timer に登録します。
3. タイマー クラスの監視メソッドを呼び出し、監視を開始します。
4. 監視プロセスは無限の while ループであり、タイム ヒープの先頭が期限切れになっているかどうかを常にチェックしています。当初は 1 秒ごとにチェックするループを考えていましたが、1 秒ごとにチェックするループにはまだ問題があると考えました。スリープ中の場合(1) タイマーが切れると、すぐに正確に実行できず、遅延が発生する可能性があるため、引き続き usleep(1000) を使用してミリ秒単位で確認し、プロセスを一時停止します。 CPU 負荷を軽減します。
コード
/*** * 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['timeout'] < $value2['timeout']) { return 1; } return 0; } /** * 插入节点 * @param mixed $value */ public function insert($value) { $value['timeout'] = time() + $value['expire']; 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['timeout'] <= time()) { //出堆或入堆 $node['repeat'] ? $this->insert($this->extract()) : $this->extract(); $hit = 1; //开启子进程 if (pcntl_fork() == 0) { empty($node['action']) ? '' : call_user_func($node['action']); exit(0); } //忽略子进程,子进程退出由系统回收 pcntl_signal(SIGCLD, SIG_IGN); } else { break; } } $t2 = microtime(true); echo ($debug && $hit) ? '时间堆 - 调整耗时: ' . round($t2 - $t1, 3) . "秒\r\n" : ''; } }
インスタンス
$timer = new Timer(); //注册 - 3s - 重复触发 $timer->insert(array('expire' => 3, 'repeat' => true, 'action' => function(){ echo '3秒 - 重复 - hello world' . "\r\n"; })); //注册 - 3s - 重复触发 $timer->insert(array('expire' => 3, 'repeat' => true, 'action' => function(){ echo '3秒 - 重复 - gogo' . "\r\n"; })); //注册 - 6s - 触发一次 $timer->insert(array('expire' => 6, 'repeat' => false, 'action' => function(){ echo '6秒 - 一次 - hello xxxx' . "\r\n"; })); //监听 $timer->monitor(false);
実行結果
私は極端な状況もテストしました。同時に、1000 個のタイマーはすべて 1 秒で期限切れになり、すべてのタイム スタックを調整するのにかかる時間はわずか 0.126 秒です。これは問題ありませんが、タイマーが毎回を調整するには、子プロセスを開始する必要があります。これには時間がかかる可能性があり、1,000 件を 1 秒で処理できない可能性があり、これは次の監視の継続的なトリガーに影響しますが、子プロセスを開始する必要はありません。 、直接実行などの場合でも、処理を完了できるはずです。 。 。 。もちろんもっと良い方法があるはずですが、今のところ思いつくのはこれだけです。
以上がPHPマルチタスク2次タイマーの実装方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。