PHP:ファイルのロック方法(排他制御)の例
PHP (55 items)
2004年11月29日
基本的には、下記のようなロジックで可能となる。
$filename = 'write.txt';
$text = 'AAA';
//ファイルのオープン
$fp = fopen($filename,'wb+');
//バッファを0に指定(排他制御の保証)
stream_set_write_buffer($fp,0);・・※1
//ファイルのロック
flock($fp, LOCK_EX);
//ファイルポインタを先頭に移動
rewind($fp);
//ファイルに書きこみ
fwrite($fp, $text);
//ロックの開放
flock($fp, LOCK_UN);
//ファイルのクローズ
fclose($fp);
※1・・
注意点としては、"stream_set_write_buffer"関数で、バッファを0にする事。
PHPマニュアルより・・
fwrite()による出力は、通常では8Kバイトがバッファされます。これは、もし同じストリームに対し出力を行おうとするプロセスが2つあったとき、いずれかのプロセスは、他方のプロセスが出力できるように8Kバイト分データを書き出したところで停止することを示しています。stream_set_write_buffer()は、streamで指定されたファイルポインタにbufferで表されたバイト数分だけ出力バッファを設定します。bufferが0であれば、書き込み操作はバッファされなくなります。これにより、fwrite()による書き込み操作が、他のプロセスが同じ出力ストリームに対して何らかの書き込み操作を行う前に完了することが保証されます。
しかし、実際はロック対象のファイル自体に
上記ロック処理を行うだけでは不完全なようである。
flock関数のPHPマニュアルを見ても、ロック処理に関する注意書きが多い。
当サイトのテストでも、ロック対象のファイルへ不正な上書き
(ロックされずに同時に二つのプロセスが書きこむ)が確認された。
よって、PHPでのファイルロック処理は、
ロック対象のファイルにファイルロック処理を行うと共に、
ロック用ファイルを別途用意し、ロック対象のファイルへの書きこみ処理の前後に、
ロック用ファイルに対してファイルロック処理を行うことで可能となる。
$filename = 'write.txt';
$lockfilename = 'lock';
$text = 'AAA';
//**ロック用ファイルのオープン**
$lockfp = fopen($lockfilename,'w');
//**ロック用ファイルのロック**
flock($lockfp, LOCK_EX);
//ファイルのオープン
$fp = fopen($filename,'wb+');
//バッファを0に指定(排他制御の保証)
stream_set_write_buffer($fp, 0);
//ファイルのロック
flock($fp, LOCK_EX);
//ファイルポインタを先頭に移動
rewind($fp);
//ファイルに書きこみ
fwrite($fp,$text);
//ロックの開放
flock($fp, LOCK_UN);
//ファイルのクローズ
fclose($fp);
//**ロック用ファイルのロックの開放**
flock($lockfp, LOCK_UN);
//**ロック用ファイルのクローズ**
fclose($lockfp);
この記事に関連した過去記事一覧は画面下にあります。
Comments
冬星 wrote:
当方でも、web ページに設置した php によるカウンターにて、月に1回程度の頻度でリセットが起きるという症状があったのですが、原因を調べて判りました。結論から言いますと flock() は問題なく、ロジックが問題でした。
//読み込み。
$contents = $file_get_contents('foo');
//$contents に対する処理。
…。
//書き込み。
$fp = @fopen('foo', 'wb');
set_file_buffer($fp, 0);
flock($fp, LOCK_EX);
rewind($fp);
fwrite($fp, $contents);
flock($fp, LOCK_UN);
fclose($fp);
のように、カウンターの処理で、読み込みと書き込みが分離していたためでした。そこで 一個の fopen(...,'r+') で一気に読み→書きを行い、それに対して flock() をかけるようにしました。
検証環境は PHP4.3.10, linux2.4.31 です。
貴記事では、どのような検証をなさったでしょうか。同様にロジックが原因…という可能性はどこかにないでしょうか。
確実にそうでなく flock() 以外原因が考えられないのであれば、やはり最小限の再現コードと検証環境を例示されては??
2005年08月22日 23時40分07秒
ossi wrote:
当方での検証ロジックは以下のようなものです。内容としては、アクセスログを記録するロジックで、サイト訪問者のアクセスの際、その情報をXMLファイルに登録or更新します。単純にカウントアップするだけではなく複雑な編集ロジックであり、またアクセスが増える度にXMLファイルのサイズが大きくなるので、編集処理自体が少々重い処理と言えます。
//XMLファイルのオープン
//ファイルがない場合は新規作成させる
$fp = fopen('log.xml','wb+');
//バッファを0に指定(排他制御の保証)
stream_set_write_buffer($fp,0);
//ファイルのロック
flock($fp, LOCK_EX);
//ファイルの読みこみ
do {
$data = fread($fp, 10000);
if (strlen($data) == 0;
break;
}
$xmldata .= $data;
} while(true);
・・・・・・
//XMLファイル編集処理($xmldataには編集後のXML文字列)
・・・・・・
$result = $xmldata
//ファイルに書きこみ
fwrite($fp,$result);
//ロックの開放
flock($p_fp, LOCK_UN);
//ファイルのクローズ
fclose($fp);
上記のように、対象ファイルのオープンと同時にロックをかけ、ロック状態のまま編集処理を行い、結果を書き込んだ後ロックを解除します。
排他不正は、アクセス数が増えて、ほとんど同時にアクセスがあった場合に発生しました。想定では、ほとんど同時にアクセスがあっても上記の排他の時点で後のユーザーの処理が待ち状態になるとしていましたが、実際は、1ユーザーのアクセスにより編集したXML文字列に加えて別ユーザーのXML編集文字列も同じファイルに書き込まれ、XMLファイルがXML形式でなくなりXMLファイルとしてはファイルが壊れてしまうといった形になりました。
以上のことから、当方での現象ではロックするファイルに対して複雑な編集処理を行っている点が問題かもしれません。これを、XMLファイルをロックするのではなく、ただロックするだけのファイルを別途用意し、それをXMLファイルのオープン・編集処理の間に挟む事で想定していた動作となりました。
DBの排他イメージでこのように使用していましたが、flockは確かに環境(OS・ファイルシステム)に依存しますし、完全には保証できないとの情報もどこかで見ましたので、少し怪しいかなと思っています。結論からすれば、これを使用するロジック次第と言えるのではないでしょうか。少なくとも当方と同じように使用している方がいるのであれば、ロックファイルを使ったほうがより完全にロックできますよという事例として、当記事を参照していただければと思います。
環境:PHP4.3.10、XREAレンタルサーバー(Linuxでバージョンは不明)
2005年08月24日 00時41分30秒
冬星 wrote:
ご返答ありがとうございます。ひとつ思ったことがあったのですが、その前に一見しまして質問させてください。
冒頭:
//ファイルがない場合は新規作成させる
$fp = fopen('log.xml','wb+');
これは新規にサイズ 0 でつくっているところですね。ファイルの存在チェックおよび、ファイルが存在した場合に開く処理が無いですが、どう開いているんでしょうか。
flock() を閉じるときにファイルポインタが違う気がしますが typo ですか。
2005年08月24日 02時52分50秒
冬星 wrote:
ファイルが存在した場合は $fp=fopen(...,'rb+') で開き flock($fp,LOCK_EX) の後で rewind($fp) されているという前提で…。同様のロジックで自分の失敗談があります。書き込む段階でそのまま fwrite() していたため、書き込む前の方がファイルサイズが大きい場合に後ろにゴミが残り xml ファイルが不正になってしまっていました。ひとつの可能性としてですが思い出しましたので…。
2005年08月24日 03時01分36秒
ossi wrote:
ファイルポインタは間違ってますね。。ソース転記ミスです。少し加工してますので。。ファイル存在チェックもしていますが、wb+でオープンして常に新しい内容(文字列)にごっそり書きかえる方法を取っていました。しかし、詰めてソースを見なおせばロック用ファイルを作成しなくとも排他制御出来るかもしれません。またゴミデータが残る現象は当方でも同じように確認していますので、参考になりました。
ご指摘ありがとうございました。
2005年08月24日 11時20分40秒
ぐぐったらトップにあったので… wrote:
ossiさんのケースですが、flockでロック権を獲得するまでそのファイルに一切の操作をしてはいけませんからライトモードで開いている時点で×です。別プロセスが書き込み操作中でもファイルサイズ0になります。もう一点は、冬星さんの指摘の通りです(ファイルの壊れ方をこの視点から見て確認してみてください)。良い方法としてはこんな感じになります。
1)ab+でファイルを開き
2)ロック権を獲得
3)ftruncateでサイズを0にする
冬星さんのケースでは、ファイルの読み取りにfile_get_contents等々の関数を使っている事が×で、これらの関数は内部的にもロック権を考慮していません(つまり変更される可能性があるファイルには使えない)。
面倒ですが読み取り時もfopenからロック権獲得の処理をしましょう。
余談ですが、一度獲得したロック権は変更してはいけません。これは一部のOSが持っている(いた?)バグで、変更する瞬間に一時的にロックが外れるので別プロセスに奪われる可能性があるためだったりします。
以上ご参考まで。
2006年03月11日 16時49分14秒
Add Comments
Trackback
暴言満載 wrote:
排他処理に stream_set_write_buffer は不要じゃね?:
PHPの話。 「set_file_buffer (stream_set_write_buffer) でバッファを0にしないと排他処理が正常に行われない」なんて感じの記述がgoogleで見つかったり、PHPのユーザーの間で常識のように語られたりするんですが、なんなんでしょうかこれ。 stack*の連載PHP set_file_buffer という関数では、書き込みバッファを 0 にするという操作をしています。なぜこんなことをするかというと、通常、あるファイルに書き込みをする際には、「バッファ」といわれる、メモリ上にある書き込む内容を一時溜め込むための領域に溜め込みます。そして、ある程度溜まった所で一括してディスクに書き込みます。バッファにある値と、ディスクにある値が異なる瞬間があるわけで、つまり、バッファがあると、せっかく flock を使ってロックしても、書き込まれる前のデータをディスクから読み出してしまうことがあるわけです。 Web Artisan Blog の PHP:ファイルのロック方法(排他制御)の例 注意点としては、
2007年01月03日 00時52分33秒
Trackback URL
http://www.res-system.com/weblog/action.php?action=plugin&name=TrackBack&tb_id=359