排他制御

ホームページを公開する場合、不特定多数の人が訪れることが前提となります。(中にはそうでない場合もありますが)
また、不特定多数の人が同時に同じページにアクセスすることも考えられます。

そんな中で動くCGIでは、同時に同じファイルにアクセスしてしまうと、正しいデータが作成できなかったり
ひどい場合、ファイルを破壊することもあります。

この問題を回避する方法が、排他制御です。

排他制御が無い場合の例(カウンター):

プロセスAプロセスB
ファイルのopen 
カウント値を読み込む(値:9)ファイルのopen
カウントアップ(値:9->10)カウント値を読み込む(値:9)
ファイルに書き込む(値:10)カウントアップ(値:9->10)
ファイルのcloseファイルに書き込む(値:10)
 ファイルのclose

上記例では、プロセスBが終了時にカウント値は11になっていないといけないのですが、読み書きのタイミングにより
10のままになっています。

また、ファイルではなくデータベースを使っている場合でも同じようなことが起きます。
これもまた、回避方法が存在します。

さて、排他制御の流れをみてみます。

プロセスAプロセスB
排他(ロック)する 
ファイルのopen 
カウント値を読み込む(値:9)ロックされている為、ファイルのopenできない
カウントアップ(値:9->10)ロック解除待ち
ファイルに書き込む(値:10)ロック解除待ち
ファイルのcloseロック解除待ち
ロック解除ロック解除待ち
 排他(ロック)する
 ファイルのopen
 カウント値を読み込む(値:10)
 カウントアップ(値:10->11)
 ファイルに書き込む(値:11)
 ファイルのclose
 ロック解除

flock()関数を使った排他制御

最も簡単な排他制御です。しかし、flock()関数はOSによっては機能しない場合があります。
ターゲットが固定的で明らかに使用可能な場合のみ使用します。

サンプル:

<?php
$fp = @fopen(""/counter/cnt.dat", "r+");

//念のため(いるのかな?)
set_file_buffer($fp, 0);
rewind($fp);
//

flock($fp, LOCK_EX);

$cnt = fgets($fp);
if(!$cnt) {
  $cnt = 1;
}
else {
  $cnt++;
}
rewind($fp);
fputs($fp, $cnt);
flock($fp, LOCK_UN);
fclose($fp);
?>

エラー処理などははぶいてしまっていますが、ファイルをオープンした後にset_file_buffer()関数で
書き込み時のバッファリングをしないように設定しています。

次に、本題の排他制御です。flock()関数を使って排他的ロックをするには、ファイルポインタとLOCK_EX
指定します。

カウントアップと書き込み処理が終了した時点で、ロックを解除します。

ロックの解除にも、flock()関数を使います。ロックを解除するには、ファイルポインタとLOCK_UN
指定します。

最後にファイルをcloseすれば終了です。