排他制御

メモ:  Category:php

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

そんな中で動く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すれば終了です。

bluenote by BBB