排他制御(ロックファイル)

メモ:  Category:php

flock() 関数を使った排他制御は、OSによって使用できる場合とできない場合があります。

そこで、OS(ファイルシステム)に依存しない汎用的な排他制御を作成します。

サンプル:

<?php
class b3lock{
    private $lockdir;
    private $lockfile;
    private $timeout;
    private $retrycount;
    
    private $lockname;
    
    function __construct($lockdir,$filename = "flock",$timeout = 60,$retrycount = 10){
        $this->lockfile = $filename;
        $this->lockdir = $lockdir;
        $this->timeout = $timeout;
        $this->retrycount = $retrycount;
    }
    
    //排他処理の開始
    function lock(){
        $file = $this->lockdir . $this->lockfile;
        //ロックファイルをリネームする(リトライ回数分リネームを試みる)
        for($i = 0;$i < $this->retrycount;$i++){
            if(file_exists($file)){
                $lfile = $file . time();
                if (@rename($file,$lfile)) {
                    //リネーム成功時、ロックファイル名をセット
                    $this->lockname = $lfile;
                    return TRUE;
                }
            }
            sleep(1);
        }
        //ロック取得ができなかった場合、ディレクトリ内の古いロックファイルを検索
        $hDir = @opendir($this->lockdir);
        if (!$hDir) return FALSE;
        while ($oldfile = @readdir($hDir)) {
            if (preg_match("/^$this->lockfile([0-9]+)/", $oldfile, $locktime)){
                closedir($hDir);
                //タイムアウトしていたら、古いロックファイルをリネーム
                $diftime = time() - $locktime[1];
                if ($diftime > $this->timeout) {
                    $curfile = $this->lockdir . $oldfile;
                    $lfile = $file . time();
                    if (@rename($curfile, $lfile)) {
                        //リネーム成功時、ロックファイル名をセット
                        $this->lockname = $lfile;
                        return TRUE;
                    }
                }
                return FALSE;
            }
        }
        closedir($hDir);
    
    }
    //排他処理の解除
    function unlock(){
        $file = $this->lockdir . $this->lockfile;
        //ロックファイルを元の名前にリネームする
        return(@rename($this->lockname, $file));
    }
}

?>

それでは、ロックまでの基本的な流れを見ていきます。

プロセスA 処理 プロセスB
指定ディレクトリにflockファイルが存在するか確認 file_exists($file)
flockファイルをリネーム(flock+時間) @rename($file,$lfile)
指定ディレクトリにflockファイルが存在するか確認(renameされているのでいない)
リトライする

基準となるファイル名と処理した時間でユニークなファイル名を作成します。

基準となるファイル名がリネームされるため、次に来た処理(プロセスB)はリネームするためのファイルが見つからないためリトライ回数分リネームを試みます。

ここまでが基本的なロックの流れです。

しかし、この方法では何らかの問題でプロセスAがリネームしたまま終了してしまうと、プロセスBはいつまでたっても次の処理ができません。この問題を解決する方法として、タイムアウトを設定します。

プロセスBがリトライしてもロックできなかったとき、ロックファイルを配置するディレクトリを検索します。

基準となるファイル名と処理した時間で作成されたファイルが見つかった場合、そのファイル名から時間の部分を取得します。

preg_match("/^$this->lockfile([0-9]+)/", $oldfile, $locktime)

現在の時間とファイルから取得した時間を比較して、タイムアウトしているようなら新たにプロセスBがリネームしてロックします。

タイムアウトしていないようなら、プロセスBはロックに失敗します。

ロックを解除するには、リネームしたロックファイルの名前を基準となるファイル名にリネームします。

これで、汎用的な排他制御ができます。

bluenote by BBB