トップへ(mam-mam.net/)

【CRON運用でありがち】PHPスクリプトの多重起動をスマートに防ぐ方法

【CRON運用でありがち】PHPスクリプトの多重起動をスマートに防ぐ方法

CRONでPHPを5分おきに実行する──それ自体は簡単。
でも、処理の遅延により前回のプロセスがまだ生きていて思わぬ多重起動を招きログ汚染やDB競合を引き起こす……そんなケースに心当たりはありませんか?
本記事では、その“起こりがち”な問題に対してシンプルかつ「基本の設定」+「安全対策」をカバーした確実な対処法をご紹介します。

CRONで5分毎にPHPファイルを起動させる設定

(例)crontab -eで設定で以下の条件で設定するとします。

-fを付けない場合

*/5 * * * * /usr/bin/php /home/mam/cron/task.php >> /home/mam/log/task.log 2>&1

-fを付ける場合

*/5 * * * * /usr/bin/php -f /home/mam/cron/task.php >> /home/mam/log/task.log 2>&1

設定したら、crontab -lで確認をしましょう。


    

PHPのソースコード

同じフルパスのファイル名が実行されていたら、処理停止するソースコード。

<?php
//CRONで呼び出された自身のPHPファイルがプロセスとして起動中かどうかを確認する
//"php -f 実行PHPファイル名" 又は "php 実行PHPファイル名" でプロセス起動されている
$processes = [];
exec('pgrep -f '.'"php.+'.preg_quote(__FILE__).'"', $processes);
if(count($processes)>1){
  echo "[".date('Y-m-d H:i:s')."]同名ファイルのプロセス(".implode(",",$processes).")が実行中の為中断(PID=".getmypid().")\n";
  exit();
}
echo "[".date('Y-m-d H:i:s')."]プロセス実行開始(PID=".getmypid().")\n";
//10秒間待つ
for($i=0;$i<10;$i++){
  //1秒待つ
  sleep(1);
  echo "    ".($i+1)."秒経過\n";
}
echo "[".date('Y-m-d H:i:s')."]プロセス実行終了(PID=".getmypid().")\n";
preg_quote(__FILE__)
., + など正規表現のメタ文字をバックスラッシュでエスケープし、__FILE__ を安全なパターン文字列へ変換
pgrep -f
"php+.ファイル名" を含むプロセスを正規表現で検索
count($processes) > 1
自身以外にも同名プロセスが存在=多重起動と判断
sleep(1)
1秒ごとに処理して経過をログに出力(任意のダミー処理)usleep(1000000)でも同じ

参考

実行ユーザー名を指定してプロセスを確認するには

-u スイッチを追加します。
pgrep -u 実行ユーザー名 -f "実行コマンドライン全体に対する正規表現構パターン"

CRONを相対パスで指定する場合

現場では「相対パス」でCRON記述されるケースもあります。
その場合、`__FILE__`とコマンドラインのパスが一致しないため、`basename(__FILE__)` による照合が有効です。
※ basename(__FILE__) はファイル名のみで判定するため、同名ファイルが複数あると誤検出の可能性があります。

*/5 * * * * php ./task.php
exec('pgrep -f '.'"php.+'.preg_quote(basename(__FILE__)).'"', $processes);