|
ロックファイル
ウェブアプリでのデータ保存
前回でデータをファイルに書き込んだり読み込んだりする方法を学びました。これでゲ
ームのハイスコアなどを保存できます。
しかし、これだけでは少し不十分です。
ウェブで公開しているゲームには多数の人が同時に参加している可能性があります。こ
のとき複数の人がいっぺんにデータを保存しようとすると誰かの分が保存されないことが
起こり得ます。
その仕組みはこう。
1. Aさんがデータファイルを読む。
2. Bさんがデータファイルを読む。
3. Aさんがデータファイルを保存。
4. Bさんがデータファイルを保存(上書き)。
「Bさんが基のデーターに修正したもの」を「Aさんが基のデーターに修正したもの」に
上書きしてしまうのでAさんの分が消えてしまいます。
データの処理はすぐ済むのでよほどのタイミングでないとそういうことは起こらないか
もしれませんが、未然に防げる事故は避けて通るのが得策です。
だからほんとうはこういう順番になるように制御したいところです。
1. Aさんがデータファイルを読む。
2. Aさんがデータファイルを保存。
3. Bさんがデータファイルを読む。
4. Bさんがデータファイルを保存。
というわけで、データ保存における横殴り衝突事故の回避策をお話します。
まず、私たちなら日常どういう流れで作業を行うか考えて見ましょう。
1. データーファイルが誰も使用していないか確認。
2. 誰かが使っていたら終わるまで待つ。
3. もし誰も使っていなければ読み込んで修正して保存。
という感じになりますね。
これをプログラムで実現しましょう。
色々方法があるかもですが、とりあえず1例を紹介します。
プログラミング
まず「誰かが使っているか」という目印として、なにか適当なファイルを作ってやり、
それで判断します。
こういう使い道のファイルをロックファイルと呼びます。
ファイル自体は何でもかまいませんが、少しでも操作が軽くなるように空ファイル
にします。
ロックファイルの操作は最初と最後にします。
この流れを文章にするとこうなります。
1. ロックファイルを見る。
2. 処理中ならしばらく待つ。
3. ロックファイルを操作する。
4. 何らかの処理。
5. ロックファイルを操作する。
このときのロックファイルの操作なのですが、今回はそのファイル名を目印にしたいと
思います。以下の様な操作をします。
処理の最初にファイル名をその時間に変更し、処理の最後に元の名前に戻す。
となります。
なので、先ず最初に C:\Program Files\Apache Group\Apache2\cgi-bin へ lockfile と
いう名前のファイルを作ります。
テキストファイルでも作って名前を変えて拡張子を取りましょう。
プログラムは以下のようになります。
#!c:/perl/bin/perl
print "Content-type: text/html; Charset=Shift_JIS\n\n";
sub lock_start {
my $lockfilename;
#'lockfile'があればロック操作して終わり
#無ければ1秒待ってやり直し
for (my $i = 0; $i < 6; $i++, sleep 1) {
if (rename('lockfile', $lockfilename = 'lockfile'.time)){;
return $lockfilename
}
}
#駄目なら戻り値無し
return undef;
}
sub lock_end {
rename($_[0], 'lockfile');
}
# ロックする
my $lockfile = lock_start() or die '処理失敗(>_<)ノ';
#何かしらの処理
print $lockfile;
# ロック解除
lock_end($lockfile);
|
いきなり難しい内容ですが、(^-^; 1つ1つ説明します。
sub lock_start{}
ロック開始のサブルーチンです。
成功すれば変更されたロックファイルの名前を返します。
sub lock_end{}
ロック解除のサブルーチンです。
ロックファイルの名前をもとに戻します。
for (my $i = 0; $i < 6; $i++, sleep 1) {}
{}の処理を6回繰り返します。
そのとき sleep 1 で1秒待ちます。
if (rename('lockfile', $lockfilename = 'lockfile'.time)){
return $lockfilename
}
'lockfile'という名前のファイルの名前を'lockfile+現在時刻'に変更(
こんな感じ→lockfile1162354423)。
それが成功したら{}を実行せよ。
rename(A,B)でAをBという名前に変更する。なのですが、
B に $lockfilename = 'lockfile'.time とかして、$lockfilenameにその値を代入するこ
とも同時にできます。
これは
$lockfilename = 'lockfile'.time;
rename('lockfile', $lockfilename);
と2行に分けても良いですが、少しでもタイムラグが出ないように1文にまとめています。
return undef;
まず1行目。
呼び出し先が戻り値の有無で成功失敗を判断する(if文)仕様なので。失敗した場合には念
入りにundefを返します。
rename($_[0], 'lockfile');
ロックファイルを元の名前に戻しています。
引数は @_ に入っているのでその1番目 $_[0] をそのまま使います。
my $lockfile = lock_start() or die '処理失敗(>_<)ノ';
成功したら戻り値を $lockfile に代入、失敗したら'処理失敗(>_<)ノ'と表示して終わり。
「A or die B」は「A さもなくば死ね」みたいな感じです。
Aの結果が真なら die B は実行されません。
lock_end($lockfile);
引数として最初に変更したファイル名を渡してやり、もとに戻します。
これでひとまずロック処理ができました。
この例では、タイムアウトが6秒でロックファイルの置き場所はtest.cgiと同じディレク
トリになっていますが、そのへんは好きに変更して下さい。
異常対応
上のプログラム上では起こりえないことですが、ロックファイルが無くなっていたり、
名前がもとに戻っていなかったりしていたら、永遠に処理が出来なくなってしまいます。
ありがちな例では、自分で間違ってファイルを削除してしまっていた、とか、めったに
無いけどまったく無いとは言えない例では、突如停電が起きて処理の途中でバッテリーも
切れてしまった、というような人為的、ハード的な障害が可能性としてはあります。
というわけでとりあえず以下のように追加します。
#!c:/perl/bin/perl
print "Content-type: text/html; Charset=Shift_JIS\n\n";
sub lock_start {
my $lockfilename;
#'lockfile'があればロック操作して終わり
#無ければ1秒待ってやり直し
for (my $i = 0; $i < 6; $i++, sleep 1) {
if (rename('lockfile', $lockfilename = 'lockfile'.time)){;
return $lockfilename
}
}
#↑6秒待っても駄目なら、ファイルを検証してみる
#まずはディレクトリ内のファイル名一覧を取得
opendir(MYDIR, '.');
my @filelist = readdir(MYDIR);
closedir(MYDIR);
#その中からルール通りのファイルをピックアップ
foreach (@filelist) {
#lockfile+数字又はlockfileというファイルは有るか?
if (/^lockfile(\d+)|^lockfile/) {
#有れば数字の部分だけ検証
#タイムアウトしたファイルだったらロック操作して抜ける
if (time - $1 > 6 and rename($_, $lockfilename = 'lockfile' . time)){
return $lockfilename
};
}
}
#ここまで来たということはロックファイルが無いということ
open(LOCK,"> $lockfilename");
close(LOCK);
return $lockfilename;
}
sub lock_end {
rename($_[0], 'lockfile');
}
# ロックする
my $lockfile = lock_start() or die '処理失敗(>_<)ノ';
#何かしらの処理
print $lockfile;
# ロック解除
lock_end($lockfile);
|
これでそこそこでまあまあになったと思います。
他にも色々方法があるのでこれ以上のものは他所のサイトにお任せします。
|
|
|