暴言満載

ネット上でぐらい暴言吐かせて・・・

排他処理に stream_set_write_buffer は不要じゃね?

2007-01-03 00:23:07 | PHP

PHPの話。 「set_file_buffer (stream_set_write_buffer) でバッファを0にしないと排他処理が正常に行われない」なんて感じの記述がgoogleで見つかったり、PHPのユーザーの間で常識のように語られたりするんですが、なんなんでしょうかこれ。

stack*の連載PHP

set_file_buffer という関数では、書き込みバッファを 0 にするという操作をしています。なぜこんなことをするかというと、通常、あるファイルに書き込みをする際には、「バッファ」といわれる、メモリ上にある書き込む内容を一時溜め込むための領域に溜め込みます。そして、ある程度溜まった所で一括してディスクに書き込みます。バッファにある値と、ディスクにある値が異なる瞬間があるわけで、つまり、バッファがあると、せっかく flock を使ってロックしても、書き込まれる前のデータをディスクから読み出してしまうことがあるわけです。

Web Artisan Blog の PHP:ファイルのロック方法(排他制御)の例

注意点としては、"stream_set_write_buffer"関数で、バッファを0にする事。

PHP 実践のツボ 〜セキュアサイト構築テクニック〜(書籍)

同時アクセス対策として排他制御をするには flock関数とset_File_Buffer関数の利用

「バッファにある値と、ディスクにある値が異なる瞬間がある」ってのは正しいね。でもその異なる値をどうやって読み出すのか。flockをしてるんでしょ?俺が作った変な解説図を参考にしてもらいたい。ところで、PHPの公式マニュアルにはこう書いてある。

fwrite() による出力は、通常では 8K バイトがバッファ されます。 これは、もし同じストリームに対し出力を行おうとするプロセスが2つあったとき、 いずれかのプロセスは、他方のプロセスが出力できるように 8K バイト分 データを書き出したところで停止することを示しています。 stream_set_write_buffer() は、stream で指定されたファイルポインタに buffer で表されたバイト数分だけ出力バッファを設定します。 buffer が 0 であれば、書き込み操作はバッファされなく なります。 これにより、fwrite() による書き込み操作が、他の プロセスが同じ出力ストリームに対して何らかの書き込み操作を行う前に 完了することが保証されます。

この記述を誤解していると思います。この記述は、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にこっそり修正)

<?php
$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);
 
$file1 = fopen('./countbysize', 'a');
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*の話ですが、企業のサイトなんで少々(タイトルどおり?)暴言吐かせていただきますが

また、r+ でのファイルのオープン時にはファイルポインタ(ファイル中のどこを参照しているかのポインタ)はファイルの先頭にあるのですが、ロックが獲得できるのを待っている間にファイルの状態が変わっている場合があります。そのときにファイルポインタが正しい位置を示すように(この場合ファイルの先頭が正しい位置)fseek や、rewind というファイルポインタの操作関数を使ってファイルポインタのリセットをしてやらないといけません。
いったい何を言ってるんでしょうか。「ファイルポインタ」はストリームごとにある。他のプロセスが何をしようと関係ない。同じプロセス内でも複数回fopenすれば独立の「ファイルポインタ」なのに。全く分かってないですね。
また、set_file_buffer のところをコメントアウトしたりして、いろいろ検証してみてください。
検証しなきゃいけないのはあなたでしょ。本当にこの文章書いた 人は検証したんでしょうかね。まあこんないい加減なこと書くぐらいですから、検証の仕方もダメなんでしょう。

Googleで「PHP 排他処理」で検索するとさっき取り上げたWeb Artisan Blogや stack* はとても上位に来ます。特にstack*は企業のサイトです。こうやって間違いが常識になるわけですね。

ジャンル:
ウェブログ
キーワード
ファイルポインタ コメントアウト ネットワークファイルシステム
コメント (2) |  トラックバック (0) |  この記事についてブログを書く
Messenger この記事をはてなブックマークに追加 mixiチェック シェア
« Apacheの設定はgo... | トップ | VMR9でインターレ... »

2 コメント

コメント日が  古い順  |   新しい順
Unknown (マナ)
2007-01-03 14:07:33
flockは他からのflockをブロックするだけでファイルへのreadは普通にできるから
「バッファにある値と、ディスクにある値が異なる瞬間」をreadされたら、
例えばカウンタだったらその瞬間をreadされちゃうと
ほんとはすでに+1の書き込みが行われてるのに
まだバッファにいるから
次のアクセスの時に前の値を読み込んでしまって
実際は2回踏まれてるのに+1しかされてない、
だからバッファを0にして書き込みがあったら即座に反映して
他のプロセスからreadがあっても常に最新状態を保ちましょうって事じゃないかな?
Unknown (マナ)
2007-01-03 16:10:46
2時間前の俺てんでダメな件
気をつけろよgoogle先生は嘘つきだぜ

コメントを投稿


コメント利用規約に同意の上コメント投稿を行ってください。
※文字化け等の原因になりますので、顔文字の利用はお控えください。
下記数字4桁を入力し、投稿ボタンを押してください。この数字を読み取っていただくことで自動化されたプログラムによる投稿でないことを確認させていただいております。
数字4桁

トラックバック

この記事のトラックバック  Ping-URL
ブログ作成者から承認されるまでトラックバックは反映されません。

あわせて読む