1つのファイルに対して多人数が同時に書き込みを行うと処理が喧嘩してファイル破損するので、同時に処理できないよう「一人目以外は拒否」または「一人ずつ順番に処理を行う」制御する仕組みをファイルロックと言います。
PHPではファイルアクセスの排他制御としてflock関数があります。
ファイルを開くときにflockを使うと、一番最初にflockを使用したユーザーにファイルアクセスの優先権が与えられ、他のユーザーはflockの設定に従い読み込み専用・flockが終わるのを待つ・flockをスキップして別の処理へ進むなどコントロールされます。
ハンドル=flock(ファイル名 , モード );
ロックモードは2種類
LOCK_SH | 共有ロック:同時読み込みできるが、書き込みできない |
---|---|
LOCK_EX | 排他的ロック:1人のみ読み書きできる |
オプションとして、通常は他のユーザーがファイル使用中の場合はロックが終わるの待機しますが、上記に「| LOCK_NB」を追加すると、ロックできない場合は待たずにflase(偽)を返します。(※Windowsでは使えない)
ロック解除モードは1種類
LOCK_UN | ロック解除します。(5.3.1以前は無くても問題なかったが5.3.2以降は必須) |
---|
1つのファイルをロックする場合は直接開くファイルをロックします。そのファイルにアクセスする全てのプログラムで同じロック方法を使用する必要があります。
$fh = fopen('sample.txt', 'r+'); //(a)上書きの場合 //$fh = fopen('sample.txt', 'c+'); //(b)5.2.6以降で新規の場合(下記注意参照) //$fh = fopen('sample.txt', 'a+'); //(c)5.2.5以前で新規の場合(下記注意参照) if (flock($fh, LOCK_EX)) { //ロック開始 //rewind($fh); //(c)の続き //処理 echo 'ロック成功'; fflush($fh); //バッファ書き出し(書き込んだ場合はロック解除前に実行) flock($fh, LOCK_UN); //ロック解除 } else { echo 'ロック失敗'; } fclose($fh);
注意 flock関数はファイルが存在しない時はロックできないので新規作成がある場合は下記のbとcのようにします。
多数のファイルにロックが必要な場合は一個一個ロックしていると時間がかかるので、ロック専用の空のファイルを一つ決めて一番最初にロックして処理が完了したら最後にロックを解除します。該当そのファイルにアクセスする全てのプログラムで同じロック方法を使用する必要があります。
$fhlock = fopen('lock.txt', 'a+'); if (flock($fhlock, LOCK_EX)) { //ロック開始 echo 'ロック成功'; //通常ファイルを開く $fh1 = fopen('sample1.txt', 'r+'); $fh2 = fopen('sample2.txt', 'r+'); //通常ファイルの処理 //通常バッファ書き出し fflush($fh1); fflush($fh2); //通常ファイルを閉じる fclose($fh1); fclose($fh2); flock($fhlock, LOCK_UN); //ロック解除 } else { echo 'ロック失敗'; } fclose($fhlock);
flockは一部のOSで使用できません、またflockを使えない他のプログラミング言語間でファイルを共有する場合などは、代替の方法としてmkdir関数によるディレクトリロック方式を使います。
手法はファイル使用時にロック用のディレクトリの存在確認をし、ディレクトリがあれば誰かがファイルを使用中、ディレクトリが無ければ使用可能なので自身でディレクトリ作成してファイルを使用し、使用後にディレクトリを削除します。
欠点は、ロックディレクトリを作成したプロセスが途中で停止するとディレクトリが削除されず残りロック状態が解除されない所です。手動でロックディレクトリを削除しないとプログラムが動かなくなります。(自動化するのであればディレクトリの作成時間からロック時間が長い場合にディレクトリを削除するようなプログラムが必要となり、さらにディレクトリ削除時にも別のロックディレクトリが必要となるので複雑です)
//(動作未検証です) // ---------- ディレクトリ作成関数 ---------- function lockDir($dir){ $retry = 10; //リトライ回数上限 $wait = 100000; //リトライ間隔(100000=0.1秒) $count = 0; //リトライ用カウンター $flag = TRUE; //成否判定(戻り値) //ディレクトリ作成判定(@でエラー表示抑制) while(! @mkdir($dir)){ $count++; if ($count >= $retry ) { //リトライ回数をオーバーしたら失敗 $flag = FALSE; break; } usleep( $wait ); //リトライ時の待機時間 } return $flag; //成功=TRUE / 失敗=FALSE } // ---------- ロック解除関数 ---------- function unlockDir($dir){ rmdir($dir); //ディレクトリ削除 } // ---------- メインブロック ---------- $lockdir='lock1'; //ロックディレクトリ名の設定 if( lockDir($lockdir) ){ //ロック作成判定 echo 'ロック中'; //ロック中の処理 unlockDir($lockdir); //ロック解除 }else{ echo 'ロック失敗'; }
ファイルロックは排他制御とも言われますがファイル破損を防止する機能しかないので、処理する内容によっては排他制御が不完全となります。
例えば一人目と二人目の文書が反目するような場合を競合状態と言いますが、ファイルロックでは単純に「一人目以外は拒否」なら先に処理した方が勝ち「一人ずつ順番に処理を行う」なら後に処理した方が勝ち、となるので負けた方の内容がいくら正しくても破棄されます。こういった場合には何らかの方法で競合解決しなければ完全な排他制御とは言えません。