タイマー タスクは、WEB アプリケーションで一般的です。PHP を使用してタイマー タスクを実装する方法には、大きく 2 つのオプションがあります。1) Crontab コマンドを使用してシェル スクリプトを作成し、スクリプト内で PHP ファイルを呼び出し、スクリプトを定期的に実行します。 2)ignore_user_abort() と set_time_limit() を一緒に使用して、スクリプトをブラウザ外で実行します。前者は Linux の特性を利用しており、PHP 自体とはほとんど関係がありません。後者は使用シナリオが限られており、1 つの HTTP リクエストでのみスクリプトをトリガーし、実行後に終了します。では、純粋な PHP を使用して純粋なタイマー タスクを実装し、コグニティブ タスクのビジネス ニーズに適応するにはどうすればよいでしょうか?
基礎知識
このプログラムは Linux で開発され、cli モードで実行されます。 ここでは基礎知識を簡単に紹介します。
CLI: PHP コマンドライン モード、一般的な WEB アプリケーションは fpm を使用します。
プロセス: プロセスは、相互に干渉することなく独立して実行され、各プロセスにはプロセス制御ブロックがあります。プロセス間通信: プロセスは独立して実行されるため、異なるプロセス間で情報を確実に交換するためのメカニズムが必要です。プロセス間通信には、主にパイプ、IPC (共有メモリ、シグナル、メッセージ キュー)、ソケットが含まれます。
PCNTL 拡張機能: 主に pcntl_alarm() 関数を使用する PHP のプロセス拡張機能。詳細は公式 Web サイトを参照してください。
実施原則
3 次元配列を使用して、実行する必要があるすべてのタスクを保存します。第 1 レベルのインデックスはタイムスタンプで、値はタスクの実行方法、コールバック パラメーターなどです。具体的な配列形式は次のとおりです。以下:
array( '1438156396' => array( array(1,array('Class','Func'), array(), true), ) ) 说明: 1438156396 时间戳 array(1,array('Class','Func'), array(), true) 参数依次表示: 执行时间间隔,回调函数,传递给回调函数的参数,是否持久化(ture则一直保存在数据中,否则执行一次后删除)
これらのタスクは、任意のクラスのメソッドにすることができます。これはスケジュールされたタスクなので、タイミングに似たものが必要です。このソリューションでは、セマフォを使用して SIGALRM 信号を現在のプロセスに毎秒送信し、信号をキャプチャし、信号処理関数をトリガーし、データをループして、信号があるかどうかを判断します。タスクの実行に必要な現在時間です。その場合は、コールバックを使用してそれをトリガーし、パラメーターをメソッドに渡します。
1 <?php 2 /** 3 *定时器 4 */ 5 class Timer 6 { 7 //保存所有定时任务 8 public static $task = array(); 9 10 //定时间隔 11 public static $time = 1; 12 13 /** 14 *开启服务 15 *@param $time int 16 */ 17 public static function run($time = null) 18 { 19 if($time) 20 { 21 self::$time = $time; 22 } 23 self::installHandler(); 24 pcntl_alarm(1); 25 } 26 /** 27 *注册信号处理函数 28 */ 29 public static function installHandler() 30 { 31 pcntl_signal(SIGALRM, array('Timer','signalHandler')); 32 } 33 34 /** 35 *信号处理函数 36 */ 37 public static function signalHandler() 38 { 39 self::task(); 40 //一次信号事件执行完成后,再触发下一次 41 pcntl_alarm(self::$time); 42 } 43 44 /** 45 *执行回调 46 */ 47 public static function task() 48 { 49 if(empty(self::$task)) 50 {//没有任务,返回 51 return ; 52 } 53 foreach(self::$task as $time => $arr) 54 { 55 $current = time(); 56 57 foreach($arr as $k => $job) 58 {//遍历每一个任务 59 $func = $job['func']; /*回调函数*/ 60 $argv = $job['argv']; /*回调函数参数*/ 61 $interval = $job['interval']; /*时间间隔*/ 62 $persist = $job['persist']; /*持久化*/ 63 64 if($current == $time) 65 {//当前时间有执行任务 66 67 //调用回调函数,并传递参数 68 call_user_func_array($func, $argv); 69 70 //删除该任务 71 unset(self::$task[$time][$k]); 72 } 73 if($persist) 74 {//如果做持久化,则写入数组,等待下次唤醒 75 self::$task[$current+$interval][] = $job; 76 } 77 } 78 if(empty(self::$task[$time])) 79 { 80 unset(self::$task[$time]); 81 } 82 } 83 } 84 85 /** 86 *添加任务 87 */ 88 public static function add($interval, $func, $argv = array(), $persist = false) 89 { 90 if(is_null($interval)) 91 { 92 return; 93 } 94 $time = time()+$interval; 95 //写入定时任务 96 self::$task[$time][] = array('func'=>$func, 'argv'=>$argv, 'interval'=>$interval, 'persist'=>$persist); 97 } 98 99 /** 100 *删除所有定时器任务 101 */ 102 public function dellAll() 103 { 104 self::$task = array(); 105 } 106 }
これは、実行する必要のあるすべてのタスクを格納する静的変数です。プロセスが SIGALRM シグナルを受信したときに、それについて考えてみてください。 signalHandler 関数がトリガーされ、配列が順次走査され、現時点で実行する必要があるタスクがあるかどうかを確認し、現時点で実行する必要があるタスクがある場合は、コールバックして削除するパラメーターを渡します。次に、永続的なタスクが実行されるかどうかを確認し、実行される場合は、引き続き現在のジョブをイベント配列に書き込み、次のトリガーを待ちます。最後に、プロセスはアラーム信号を設定します。このタイマーは、一度トリガーされると、内側から再度トリガーされ、自己ループの目的が達成されることがわかります。
1 <?php 2 3 class DoJob 4 { 5 public function job( $param = array() ) 6 { 7 $time = time(); 8 echo "Time: {$time}, Func: ".get_class()."::".__FUNCTION__."(".json_encode($param).")\n"; 9 } 10 }
これは説明の便宜のために、Timer クラスとコールバックがどのようなものかを見てみましょう。
1 <?php 2 3 require_once(__DIR__."/Timer.php"); 4 require_once(__DIR__."/DoJob.php"); 5 6 7 Timer::dellAll(); 8 9 Timer::add( 1, array('DoJob','job'), array(),true); 10 11 Timer::add( 3, array('DoJob','job'),array('a'=>1), false); 12 13 echo "Time start: ".time()."\n"; 14 Timer::run(); 15 16 while(1) 17 { 18 sleep(1); 19 pcntl_signal_dispatch(); 20 }
ここでは 2 つのジョブが登録されており、タイマーが実行されて、信号トリガー アクションがキャプチャされない場合、事前に登録された処理関数はトリガーされません。セルフループタイマーが開発されました。実行結果は次のとおりです。
シーン クラスによって追加されたタスクと同様に、2 つのタスクが 90 で実行されました。1 つはパラメーターのない永続ジョブで、もう 1 つはパラメーターのある非永続ジョブで、その後、非永続ジョブは実行されなくなります。実行します。
概要
現在のプロセスはシグナルを受信する前に終了できません。ここでは、実際の運用環境では、そのような前提条件を作成する必要があります。サービスは常に実行されており、IO アクセスやソケット接続の待機中など、プロセスがブロックされても、現在のサービスは終了しません。常に実行されているサービス
現在、PHP は秒単位のトリガーのみをサポートしており、より短い時間単位はサポートしていません。スケジュールされたタスクには基本的にこれで十分です