PHPでのpcntlシグナルの処理

PHPでの信号処理に関する記事はすでにいくつか 書かれています。 ただし、このトピックは間接的にのみ説明されています。



PHP 5.5がすでにリリースされており、5.2がすでに古いことを知っていることをすぐに予約しますが、PHP 5.2で問題を正確に解決する必要がありました。 新しいバージョンのPHPを使用している幸運な人のために、記事の終わり近くで書きます。



PHPでのpcntlシグナルの処理は、ハンドラー関数をpcntl_signal()関数に渡すことで行われ、各シグナルで1つのハンドラーのみをハングさせることができます。 後続の各ハンドラーは前のハンドラーを置き換えますが、通知はありません。



つまり 複数のサードパーティライブラリを使用し、それぞれが何らかの信号を処理する必要がある場合、1つのライブラリのみが信号を処理します。 サードパーティのライブラリは、シグナル処理のコールバックのみを提供する必要があり、ライブラリユーザーは、シグナルハンドラでこれらのコールバックを呼び出す必要があります。



例を挙げます。

2つのサードパーティライブラリがロギングに2つの異なるライブラリを使用し、両方のライブラリがログファイルがローテーションされたときに再度開くためにSIGHUPシグナルを処理する必要があるとします。 それは次のように起こります。 ログローテーションを行うデーモンは、ログファイルを移動し、このログファイルを使用するすべてのプロセスにSIGHUPシグナルを送信します。 ログファイルを移動した後、信号処理の前に、プロセスは同じファイルへの書き込みを続けます。 この信号を正しく処理した後、プロセスは新しいファイルへの書き込みを開始する必要があります。

シグナルハンドラはこれらのライブラリのいずれかに属しているのではなく、アプリケーションの一部である必要があります。 ライブラリ自体のシグナルハンドラを呼び出す必要があります。



宣言(ティック= 1)



バージョン5.3より前では、シグナルハンドラーを呼び出すには、 declareコンストラクト(ticks = 1)を使用する必要があります。 通常のphpプログラマーにとって、このような構造は複雑です。 マニュアルを読むとき、特にC ++や実行制御構造を持つ他の言語で専門的にコーディングしていない人にとっては、それがどのように機能するかはそれほど明確ではありません。

declare構文はグローバルスコープで使用でき、それに続くすべてのコードに影響します(ただし、declareファイルが含まれている場合、親ファイルには影響しません)。
ここでは明らかに例を検討する必要があります。



性能



この設計がパフォーマンスにどのように影響するかは書かれていないため、ベンチマークを作成しました。 ファイル:

Example.php:

<?php class Example { public function run() { for($i = 0; $i < 10000000; $i++); } }
      
      





testWithTicksSpeed.php:

 <?php declare(ticks = 1); require_once __DIR__ . '/Example.php'; $example = new Example(); $example->run();
      
      





testWithoutTicksSpeed.php:

 <?php require_once __DIR__ . '/Example.php'; $example = new Example(); $example->run();
      
      





私はすぐにテストが合成であると言わなければなりません、なぜなら データベースへのアクセス、ファイルの読み取りなどはありません。 そのパフォーマンス宣言(ticks = 1)は影響しません。

結果:

 mougrim@mougrim-pc:pcntls-signals$ time php testWithTicksSpeed.php complete, process time: 11 real 0m10.186s user 0m4.448s sys 0m5.732s mougrim@mougrim-pc:pcntls-signals$ time php testWithoutTicksSpeed.php complete, process time: 2 real 0m1.515s user 0m1.504s sys 0m0.008s
      
      





違いは〜6.7倍です。 実際のプロジェクトでは、違いは確かに少なくなりますが、信号をキャッチする必要がないリソースと時間を無駄にしたくありません。



リソースを保存する方法を理解するには(常にdeclareを呼び出すとは限りません)、この宣言の動作を理解する必要があります。 実験中に、次のことがわかりました。

このコードは機能します:

 class Test_Signal { //    public function declareTicks() { echo "declare ticks\n"; declare(ticks = 1); } public function run() { //    pcntl_signal //     } } $test = new Test_Signal(); $test->run();
      
      





このコードは機能しません:

 class Test_Signal { public function run() { $this->declareTicks(); //    pcntl_signal //     } public function declareTicks() { echo "declare ticks\n"; declare(ticks = 1); } } $test = new Test_Signal(); $test->run();
      
      





つまり declareが特定のコードブロックを指定していない場合、その後のすべてのコードに対して有効です。 そして、ここでは、インタープリターによって処理され、OPコードに変換されたコードを意味します。

わかりやすくするために、ベンチマークの例を検討してください。

Exampleクラスでは、信号が処理されます。

 declare(ticks = 1); require_once __DIR__ . '/Example.php'; $example = new Example(); $example->run();
      
      





Exampleクラスでは、信号は処理されません。

 require_once __DIR__ . '/Example.php'; declare(ticks = 1); $example = new Example(); $example->run();
      
      







微妙



シグナルハンドラをテストすると、奇妙なことが発見されました。 ある時点で、デーモンは同じ信号を何度も処理し始めたため、メインスクリプトコードは実際には実行されませんでした。 SIGTERMシグナルを1回送信すると、デーモンはそれを何度も処理し始めました。 知識のある人々と話をした後、信号プロセッサはできるだけ小さく、メモリを割り当てないことが判明しました。 これは、メモリの割り当て中にシグナルプロセッサを呼び出すことができ、プロセッサ内のメモリの割り当てが損傷や予測できない結果を招く可能性があるためです。 宣言(ticks = 1)を使用するときのシグナルハンドラーは最小限である必要があります。たとえば、フラグを設定し、直接処理はスクリプトのメインループ内にある必要があります。



これを確認するには、十分な知識がありません。 私はC / C ++を開発していませんが、以下に説明するMougrim_Pcntl_SignalHandlerを使用し、信号処理中にメモリを割り当てない場合、この問題は再現されなくなりました。



PHP 5.3での信号処理



PHP 5.3では、素晴らしいpcntl_signal_dispatch()関数が導入されました。 一番下の行は、宣言を宣言しない場合(ticks = 1)、信号はキューに蓄積され、pcntl_signal_dispatch()関数を呼び出すと、蓄積された信号のハンドラーが呼び出されます。 同じシグナルが複数回送信された場合、ハンドラーも複数回呼び出されます。 この関数はパフォーマンスの問題を解決し、シグナルハンドラを最小化します。 処理はどこでも発生しませんが、pcntl_signal_dispatch()呼び出し中にのみ発生します。



シグナルハンドラー



5.2のシグナルハンドラの例、ファイルsrc / lt5.3 / Mougrim / Pcntl / SignalHandler.php:

 <?php declare(ticks = 1); /** * @author Mougrim <rinat@mougrim.ru> */ class Mougrim_Pcntl_SignalHandler { /** * @var callable[] */ private $handlers = array(); private $toDispatch = array(); /** *    * * @param int $signalNumber  ,  SIGTERM * @param callable $handler -  $signalNumber * @param bool $isAdd  true,     */ public function addHandler($signalNumber, $handler, $isAdd = true) { $isHandlerNotAttached = empty($this->handlers[$signalNumber]); if($isAdd) $this->handlers[$signalNumber][] = $handler; else $this->handlers[$signalNumber] = array($handler); if($isHandlerNotAttached && function_exists('pcntl_signal')) { $this->toDispatch[$signalNumber] = false; pcntl_signal($signalNumber, array($this, 'handleSignal')); } } /** *    */ public function dispatch() { foreach($this->toDispatch as $signalNumber => $isNeedDispatch) { if(!$isNeedDispatch) continue; $this->toDispatch[$signalNumber] = false; foreach($this->handlers[$signalNumber] as $handler) call_user_func($handler, $signalNumber); } } /** *      * * @param int $signalNumber  ,  SIGTERM */ public function handleSignal($signalNumber) { $this->toDispatch[$signalNumber] = true; } }
      
      





ハンドラーは2つの問題を解決します。

1)pcntl_signal_dispatch()をエミュレートします。

2)1つの信号に対して複数のハンドラー関数を使用できます。



5.3以降のシグナルハンドラの例、ファイルsrc / gte5.3 / Mougrim / Pcntl / SignalHandler.php:

 <?php namespace Mougrim\Pcntl; /** * @package Mougrim\Pcntl * @author Mougrim <rinat@mougrim.ru> */ class SignalHandler { /** * @var callable[] */ private $handlers = array(); private $toDispatch = array(); /** *    * * @param int $signalNumber  ,  SIGTERM * @param callable $handler -  $signalNumber * @param bool $isAdd  true,     */ public function addHandler($signalNumber, $handler, $isAdd = true) { $isHandlerNotAttached = empty($this->handlers[$signalNumber]); if($isAdd) $this->handlers[$signalNumber][] = $handler; else $this->handlers[$signalNumber] = array($handler); if($isHandlerNotAttached && function_exists('pcntl_signal')) { $this->toDispatch[$signalNumber] = false; pcntl_signal($signalNumber, array($this, 'handleSignal')); } } /** *    */ public function dispatch() { pcntl_signal_dispatch(); foreach($this->toDispatch as $signalNumber => $isNeedDispatch) { if(!$isNeedDispatch) continue; $this->toDispatch[$signalNumber] = false; foreach($this->handlers[$signalNumber] as $handler) call_user_func($handler, $signalNumber); } } /** *      * * @param int $signalNumber  ,  SIGTERM */ private function handleSignal($signalNumber) { $this->toDispatch[$signalNumber] = true; } }
      
      





このハンドラーが解決する問題は1つだけです。複数のシグナルハンドラー関数を使用できます。 クラスインターフェイスは、クラスMougrim_Pcntl_SignalHandlerのインターフェイスと同じです。



使用例



最後に、使用例のファイル:

signalExampleRun.php:

 <?php //    SignalHandler,     declare(ticks = 1); require_once dirname(__FILE__) . "/src/lt5.3/Mougrim/Pcntl/SignalHandler.php"; require_once dirname(__FILE__) . "/SignalExample.php";; $signalHandler = new Mougrim_Pcntl_SignalHandler(); $signalExample = new SignalExample($signalHandler); $signalExample->run();
      
      





SignalExample.php:

 <?php class SignalExample { private $signalHandler; public function __construct(Mougrim_Pcntl_SignalHandler $signalHandler) { $this->signalHandler = $signalHandler; } public function run() { //    SIGTERM $this->signalHandler->addHandler(SIGTERM, array($this, 'terminate')); //    SIGINT $this->signalHandler->addHandler(SIGINT, array($this, 'terminate')); while(true) { $this->signalHandler->dispatch(); //   echo " \n"; usleep(300000); } } public function terminate() { //  SIGTERM  // ... echo "terminate\n"; exit(0); } }
      
      





5.3以降の例も同様です。src/ gte5.3 / Mougrim / Pcntl / SignalHandler.phpを接続し、クラス\ Mougrim \ Pcntl \ SignalHandlerを使用するだけです。



結論



1)PHP 5.3以降を使用していて 、暗黙の問題を回避したい場合は、declareコンストラクト(ticks = 1)を使用しないください。

2)宣言(ティック= 1); 条件付き構造および関数呼び出しとは独立して動作し、宣言宣言後にティックに「ロード」されたコードで動作します(ティック= 1)。

3)PHP 5.2でMougrim_Pcntl_SignalHandlerを使用する場合、信号を処理する必要のあるメインプログラムループを使用して、クラスまたはコードを含むファイルに接続する必要があります。

4)なぜなら declare(ticks = 1)を使用すると、アプリケーションの実行速度が低下するため、信号処理がある場所でのみこの構造を宣言する必要があります。



気にする人は、\ Mougrim \ Pcntl \ SignalHandlerおよびMougrim_Pcntl_SignalHandlerクラスのソースコードはgithubにあります。



* UPD。* 最初のコメントで、 PsychodelEKSは、pcntl_signal_dispatch()が呼び出されたときにシグナルハンドラーが呼び出されても、ハンドラーであり、たとえばsystem()を介してその下からプログラムを実行すると、実行中のプログラムはシグナルを処理できないことを示唆しています.k。 それ自体がシグナルハンドラになります。 したがって、Mougrim_Pcntl_SignalHandlerで行われるように、ハンドラーが個別に呼び出されるように、\ Mougrim \ Pcntl \ SignalHandlerクラスのコードをわずかに変更します。



* UPD2。*バグを修正し、 最初のコメントによると、\ Mougrim \ Pcntl \ SignalHandlerクラスを修正し、pcntl_signal()に渡されたハンドラーの外部で直接信号処理が行われるようにしました(このような直接ハンドラーは再入力されます)。



All Articles