久々のPHP。
Crontabで定期的に処理をさせようと思うのですが、1つの処理が終わる前に次の処理をする時間が来てしまって二重起動してしまうことを防ごうって感じの処理です。たぶんこの界隈では昔からよくある。
(やり方とかまとめて見たつもりだけど間違ってる部分あればツッコミ下さい!)
##手法色々
調べた感じで色々あるみたい
###①プロセスをチェック
PSコマンドとgrepコマンドで実行するファイル名を含んだプロセスを調べて通常時よりもプロセス数が増えていたらプロセスが実行中と判断して次のプロセスを実行させないやり方。そこまで重く無い処理であればこれでも大丈夫みたい
例)
- 通常時:
ps aux | grep cli.php
で調べると grepのプロセスは発生する(プロセス数=1)
$ ls
cli.php
$ ps aux | grep cli.php
n0bisuke 8399 0.0 0.1 114492 904 pts/3 S+ 11:03 0:00 grep cli.php
- プロセス実行時: grepに加えてphpを実行中. (プロセス数2)
プロセス実行してみる
$ php cli.php
実行中に別タブなどでターミナルでps aux | grep cli.php
を実行してみる
$ ps aux | grep cli.php
n0bisuke 8611 0.6 1.6 346640 9804 pts/1 S+ 11:07 0:00 php cli.php
n0bisuke 8618 0.0 0.1 114492 900 pts/3 S+ 11:07 0:00 grep cli.php
通常時はgrepだけだったプロセスに加えてphpのプロセスが実行されていることが分かります。
$ php cli.php
多重起動はできません。
起動しようとしても二重起動は出来ません。
参考: コマンドライン版phpの簡易多重起動防止 at softelメモ -
###②ロックファイルを利用
一番ポピュラーなやり方みたい。プログラムの初めにロックファイル(実際はただのテキストファイルなど)を作成し、プログラムの終わりにロックファイルを削除する。このファイルが存在している間はプロセス実行中と判断して次のプロセスは実行させないやり方。
例)
- 通常時: ロックファイルは存在しないので 'php cli.php'は実行できます。
$ ls
cli.php
$ php cli.php
- プロセス実行時: プロセス実行時にはlock.txtが作成されています。この状態で同じ処理は出来ません。
$ ls
cli.php lock.txt
$ php cli.php
多重起動はできません。
###③セマフォを利用
共有メモリを見て、メモリが既に使われていたらプロセスが実行中と判断して二重実行を防ぐやり方(っぽい? いまいち理解しきれてない)。
sem_get()関数を使ってセマフォIDを取得すればカンタンに条件分岐は出来そう。
ただ、デフォルトではsem_get()は使えないらしくPHP Fatal error: Call to undefined function sem_get()
とエラーが出たので実際には試していない。
参考: Linuxキーワード - セマフォ とは:ITpro -
参考: バッチファイルの二重起動の防ぎ方
##実際のコード
①だけだと、何らかの処理(例えばvimで編集している最中など)でcli.phpが使われているときにphpを実行しているでは無いのに二重起動と判断されてしまう場合がありそうだったので、①と②で二重チェック
をしてみました。(追記: ②だけで問題無いので実際には①はいらないです笑 @tkuchiki さんより!)
だいたいバッチ処理はDBに保存する系が多いと思うのでトランザクションもしておいた例です。
参考: PHP: mysqli::commit - Manual -
参考URLたちを合わせただけですが笑
<?php
/**
* 2重起動防止① プロセスチェック
*/
//同じphpを起動している他のプロセスを探し
exec("ps aux | grep cli.php", $output, $result);
//2個以上見つかれば中止
if(count($output) > 2){
echo '多重起動はできません。\n';
exit();
}
/**
* 2重起動防止 ②-A ロックファイル
*/
// 作成するファイル名の指定
$lock_file = __DIR__ . '/lock.txt';
// ファイルの存在確認
if( file_exists($lock_file) ){
echo "多重起動はできません。\n";
exit(0);
}
// 多重起動防止用ロックファイル作成
touch( $lock_file );
/**
* 処理Start
*/
$mysqli = new mysqli('localhost','n0bisuke-user','passwd','n0bisuke-DB');
//接続をチェック
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
//autocommitをoff
$mysqli->autocommit(FALSE);
$sql = "INSERT INTO `user` (`name`) VALUES (`n0bisuke`)";
$mysqli->query($sql);
//トランザクションをコミット
if (!$mysqli->commit()) {
print("Transaction commit failed\n");
exit();
}
/**
* 処理End
*/
//②-B ロックファイルの削除
unlink( $lock_file );
##おまけ
Crontabの設定はこの辺を参考にしました。