おもしろいくらいにカウンターが壊れるんですよ。
こりゃいかんという事でソースを見直してみました。
phpで作ってます。
まずは元のソース。
[ver.1]
1| $fp = fopen("./count.txt","r");
2| flock($fp,LOCK_SH);
3| $count = fgets($fp,1024);
4| flock($fp, LOCK_UN);
5| fclose($fp);
6|
7| $count = $count + 1;
8|
9| $fp = fopen("./count.txt","w");
10| flock($fp,LOCK_EX);
11| fputs($fp,$count);
12| flock($fp,LOCK_UN);
13| fclose($fp);
14|
[EOF]
これはやっぱりあれですかね。
読み込んでカウントして書き込む前に、ちがうプロセスがカウントされる前の数字を読み込むんでカウンター数値が変になるんですかね。
Aプロセスが1〜5行目でカウンター数値を読み込む。
仮にその数値が10だとする。
Aプロセスが7行目でプラスして11にする。
Aプロセスが11にした数値を9〜13行目でカウンターファイルに書き込む前にBプロセスが1〜5行目でカウンター数値を読み込む。
その数値はまだ10のままなので10を読み込む。
Aプロセスが9〜13行目を実行して11という数値をカウンターファイルに書き込む。
Bプロセスが7行目で数値にプラスして11にする。
Bプロセスが9〜13行目でカウンターファイルに11を書き込む。
と、AプロセスとBプロセスがアクセスしてるんでカウントは2回されて12になるはずですが、上記のような事がおこっているんで実際は11になっていると思われ。
で、実際そうなるか試してみました。
[test1]
1| for($i = 0; $i < 10000; ++$i)
2| {
3| $fp = fopen("./count.txt","r");
4| flock($fp,LOCK_SH);
5| $count = fgets($fp,1024);
6| flock($fp, LOCK_UN);
7| fclose($fp);
8|
9| $count = $count + 1;
10|
11| $fp = fopen("./count.txt","w");
12| flock($fp,LOCK_EX);
13| fputs($fp,$count);
14| flock($fp,LOCK_UN);
15| fclose($fp);
16| }
17|
[EOF]
ver.1のソースをそのまんま使ってます。
コレをほぼ同時に2つの違うプロセスに実行させます。
カウンターファイルの数値は0から始めます。
するとどうでしょう。
本来なら20000という数値になっていてほしいですが、結果は5226といった数値になりました。
全然だめですねー。
そこでちょっと書き方を変えてみました。
読み込みと書き込みを一緒にロックしてみました。
[ver.2]
1| $fp = fopen("./count.txt","r+");
2| flock($fp,LOCK_EX);
3| $count = fgets($fp,1024);
4| $count = $count + 1;
5| rewind($fp);
6| fputs($fp,$count);
7| flock($fp,LOCK_UN);
8| fclose($fp);
9|
[EOF]
で、またテストしてみました。
[test2]
1| for($i = 0; $i < 10000; ++$i)
2| {
3| $fp = fopen("./count.txt","r+");
4| flock($fp,LOCK_EX);
5| $count = fgets($fp,1024);
6| $count = $count + 1;
7| rewind($fp);
8| fputs($fp,$count);
9| flock($fp,LOCK_UN);
10| fclose($fp);
11| }
12|
[EOF]
そして結果は、18171になりました。
test1よりは20000に近づいていますよ。
しかあし、まだダメですよ。
じゃあ、これならどうだっ!
[ver.3]
1| $lock_fp = fopen("./lock.txt","w");
2| flock($lock_fp,LOCK_EX);
3|
4| $fp = fopen("./count.txt","r+");
5| flock($fp,LOCK_EX);
6| $count = fgets($fp,1024);
7| $count = $count + 1;
8| rewind($fp);
9| fputs($fp,$count);
10| flock($fp,LOCK_UN);
11| fclose($fp);
12|
13| flock($lock_fp,LOCK_UN);
14| fclose($lock_fp);
15|
[EOF]
こうなったらロックファイルを使ってやる!ということですよ。
そしてテスト。
[test3]
1| for($i = 0; $i < 10000; ++$i)
2| {
3| $lock_fp = fopen("./lock.txt","w");
4| flock($lock_fp,LOCK_EX);
5|
6| $fp = fopen("./count.txt","r+");
7| flock($fp,LOCK_EX);
8| $count = fgets($fp,1024);
9| $count = $count + 1;
10| rewind($fp);
11| fputs($fp,$count);
12| flock($fp,LOCK_UN);
13| fclose($fp);
14|
15| flock($lock_fp,LOCK_UN);
16| fclose($lock_fp);
17| }
18|
[EOF]
で、結果。
祝20000!!!!
ちなみにver.1とロックファイルの組み合わせでもしっかり20000でした。
でもfopenしてる回数が多いんでtest3と比べると3倍位の遅さでした。
要するにカウンター壊したくないんならロックファイルを使えやということでしょうかね。
テスト環境はこんな感じ。
OS:Windows 2000
言語:PHP4.1.2
テスト環境を変えたらtest2でも正常に20000になりました。
OS:FreeBSD 4.6
言語:PHP4.3.9
OSがunixだとflockの仕様が変わってくるんですかね?
もっとスマートで良い方法がありましたらおせーて下さい。