PHP 〜排他制御〜

 今回は PHP に初めての読み物です。
ウェブプログラミングで厄介なのが複数の同時アクセスによるコンフリクト(衝突)です。
カウンタを実装するのにデータファイルにアクセスする箇所での処理順序としては以下の様な感じになると思います。
  1. カウント値を保存しているファイルを開く。
  2. ファイルからカウント値を読み込む。
  3. カウント値をインクリメント(+1)。
  4. インクリメントされた値をファイルに保存する。
大体はこんな感じになると思います。
しかし、Bを実行中に他からAが実行された場合ファイルにまだインクリメントされた値は保存されていないので同じ値を書き込んでしまう場合が発生します。こういった事が発生しないようにするにはまず@を実行する前に他から@が実行出来ないようにロックします。ロックされている間は他からのアクセスは待ち状態になります。そしてCが終了した時点でロックを解除する事で待ち状態になっているものが@を実行するようにします。この事を排他制御と言います。排他制御はウェブプログラミングに限らずマルチスレッド環境でも使用されます。
ぱっと思いつくのは flock () を使用する事だと思われがちですがこれはプロセスレベルでしか排他制御が行えないためファイルの排他制御に向いていません。また flock () は fopen () で開いた後でしか使用出来ないので fopen () でファイルサイズを 0 にした時などに不具合が発生します。なので今回はファイルシステムを使用した排他制御を実装します。具体的にはロックをする時にファイルを作成してロック解除でファイルを削除するようにします。ソースにすると以下の様な感じです。
// ロック開始
function lock () {
  while (1) {
    if (@mkdir ("lock", 0777)) {
      break;
    } else {
      sleep (1);
    }
  }
} // lock

// ロック終了
function unlock ()
{
  @rmdir ("lock");
} // unlock
lock () を実行する事で "lock" と言うディレクトリを作成します。それで既に同じ名前のディレクトリが存在する場合は
一度スリープして再度ディレクトリを作成しに行きます。これで待ち状態にする事が可能です。
ロック解除は作成したディレクトリを削除するだけです。ディレクトリが削除された時点で lock () 内でループしていたプロセスは
ディレクトリを作成してこの関数を抜け出します。同じ名前で複数のディレクトリは作成出来ませんので
この辺でファイルシステムを利用しています。
この排他制御をクラスで実装したものを倉庫に置いてありますのでご自由にダウンロードして下さい。
追記)ダラダラと排他制御について書きましたが上記の排他制御は正常動作が行えない状態がある事が判明しました。今まではうまくいっていると思っていましたが読み込み途中にブラウザを閉じられた場合に unlock () されない場合が存在するようです。これは C++ にあるデストラクタがあれば問題ないと思われますが PHP にはそれが無いので回避する事は無理なようです。では一体どうすればいいかと言いますと上で排他制御に向いていないと言いきってしまっている flock () を使用します。プロセスレベルでのロックしか出来ませんが PHP が動作するサーバ上では全て一つのプロセスとして動作しているようです※1。なので潔く flock () を使う事をオススメします。ただし fopen () によるファイルサイズ 0 クリアの問題は flock () では回避出来ないので設計段階でその様な事が発生しない様にするしかありません。
※1 少なくとも仁丹が使用しているサーバでは同じプロセスでした。