
PHPの話。 「set_file_buffer (stream_set_write_buffer) でバッファを0にしないと排他処理が正常に行われない」なんて感じの記述がgoogleで見つかったり、PHPのユーザーの間で常識のように語られたりするんですが、なんなんでしょうかこれ。
Web Artisan Blog の PHP:ファイルのロック方法(排他制御)の例
PHP 実践のツボ 〜セキュアサイト構築テクニック〜(書籍)
「バッファにある値と、ディスクにある値が異なる瞬間がある」ってのは正しいね。でもその異なる値をどうやって読み出すのか。flockをしてるんでしょ?俺が作った変な解説図を参考にしてもらいたい。ところで、PHPの公式マニュアルにはこう書いてある。
この記述を誤解していると思います。この記述は、flockやらセマフォやらmutexで排他制御されていない同時書き込みで何が起こるかを書いていて、flockを用いた排他制御の時には関係のない話です。
つまり、'a'モードでfopenしたときとか、ネットワークみたいなシーク不能なストリームに対して、例えば10MBのデータを1回のfwriteで書き込むときに、バッファが有効でなければ対応するOS呼び出し(システムコール)が1回になるとかそういうこと。
(しかし「他方のプロセスが出力できるように 8K バイト分 データを書き出したところで停止する」(英語版: each is paused after 8K of data to allow the other to write )ってのもまた誤解を生む書き方だなあ。)
検証コード(06/01/04にこっそり修正)
$file2 = fopen('./countbystring', 'a+');
if(!flock($file2, LOCK_EX)) exit(1);
$tmp = fgets($file2);
if(empty($tmp)) $tmp = '0';
$tmp++;
ftruncate($file2, 0);
fwrite($file2, strval($tmp));
fclose($file2);
fwrite($file1, '0', 1);
fclose($file1);
このコードの前提条件は「flockが成功した場合はきちんと機能する」「追記モードで開いたファイルへの書き込みは全て行われる」。NFSとかのネットワークファイルシステムで行うと2番目の条件が満たされないらしい。
まともなOSでローカルのHDD上とかでこれを実行すれば、countbysize のサイズと countbystring の内容は一致します。 stream_set_write_buffer でバッファを無効にしてないからうまく一致しないという環境は存在するでしょうか?
そういえば、この記事を書くに当たってPHPの排他処理に関してGoogleで調べたんですが、fcloseの前にflockを解除しているプログラムをよく見かけるんですけど、あれは何を意図したものなのでしょうか。flock解除したらその後は他のプロセスが割り込む可能性があります。つまりfcloseの前に他のプロセスが同じファイルに何かするかもしれないんです。たしかに stream_set_write_buffer でバッファを無効にしてれば割り込まれても問題はないかもしれないけど、そんなのは本末転倒で、ストリームに書き込みを行った場合fflushまたはfcloseまでのいつか分からない瞬間に書き込みが行われると考えるのが本来で、fcloseをロックの範囲外に出すという考え方が間違ってます。
fcloseでflockが解除されるのはちゃんと意味があって、「mallocしたらfreeしなきゃいけない」のとは訳が違うんだな。
これまたさっきのstack*の話ですが、企業のサイトなんで少々(タイトルどおり?)暴言吐かせていただきますが
Googleで「PHP 排他処理」で検索するとさっき取り上げたWeb Artisan Blogや stack* はとても上位に来ます。特にstack*は企業のサイトです。こうやって間違いが常識になるわけですね。










「バッファにある値と、ディスクにある値が異なる瞬間」をreadされたら、
例えばカウンタだったらその瞬間をreadされちゃうと
ほんとはすでに+1の書き込みが行われてるのに
まだバッファにいるから
次のアクセスの時に前の値を読み込んでしまって
実際は2回踏まれてるのに+1しかされてない、
だからバッファを0にして書き込みがあったら即座に反映して
他のプロセスからreadがあっても常に最新状態を保ちましょうって事じゃないかな?
気をつけろよgoogle先生は嘘つきだぜ