ロック専用ファイル
そふぃのPHP入門 >> PHP実践リファレンス >> ファイル操作 >> ロック専用ファイル

ロック専用ファイル

ロック専用ファイル

ここで素朴な疑問を解消してファイルロック関連はとりあえずおしまいです。このコンテンツの中でfile()関数やfile_get_contents()など、一気にファイルを読み込める関数を紹介しましたが、そういった関数を使って先にファイルを読み込んでおきたい時はどうしましょう。ファイルロックしたくてもflock()関数は引数にファイルポインタが必要ですからどうしてもファイルを開いた後でないとできません。そういった関数は使わない!!と言い切ってしまうとそれでおしまいになってしまいますが、確かにカウンター程度ではあまり使う必要はありません。

これが掲示板だとかアクセス解析だとかになってくると、ログの量が何十行とか何百行にもなりますし、それをいちいちループで処理していくのも面倒なのでfile()関数やfile_get_contents()などで一気に読み込んでしまいたいもんです。

ファイルを一気に読み込めるような関数には元々ロック機能とかも入ってるんじゃないの?と思うかもしれませんが、そういった機能は残念ながら入ってません。読み込みと書き込みを別に処理する事になるので、いくら書き込む方でロックをかけても読み込む方の関数でロックをかけれませんので無意味になります。この場合もロックをかけずに「w」あたりで開いたらしっかりとログが飛びます。

どうするかというと、これもflock()が使えるというのが前提なのですが、ファイルロック専用のファイルを作り、それにロックをかける事にします。読み込みの処理も書き込みの処理もそのロックファイル(ロック専用に作ったファイル)に排他制御が効いている間にやってしまいます。

ロック専用ファイルを使ったサンプル

言葉で説明してる方が分かりにくいのでサンプルです。サンプルの内容は"この行が追加されます\n"という文字列を書き込んでいくだけサンプルです。

最新のログをファイルの一番上に書き込みたい時などによく使われるのですが、まず古いログ(というか現在のログ)をfile()関数やfile_get_contents()関数でファイルの内容を変数に格納しておきます。変数に格納して安心したところで、ログファイルを「w」で開いて真っ白(空)な状態にします。その後、新しく追加したい内容を書き込んだら、変数に格納しておいた古いログを書き込みます。すると最新ログが一番上というファイルが出来上がりますね。

この手法は良く使われているのですが、問題はロックができない事です。そのまま運用するとログが吹っ飛びかねないというのはロック関連のページを読んできてくださった方なら分かると思います。file()関数やfile_get_contents()関数ではファイルポインタを取得する必要がないためにflock()もかけれませんね。そこで、ロック専用のファイル「lock.txt」を作って置いて下さい。どこに置くか迷う人はとりあえず同一ディレクトリでいいです。ここではファイル名を「lock.txt」としていますが名前は何でもいいですし、ロックのためだけに使うので中身も空で構いません。

このスクリプトでは、最初の方に書き込み処理用に、最後の方で出力処理用にと2回ファイルを読み込んでいます。ロック専用ファイルでの排他制御はこの2つがすべて無事に終わるまで続けます。このサンプルにはエラー制御演算子「@」はつけてません。

このサンプルはそのまま運用すると延々、"この行が追加されます\n"を一番上の行に書き続けます。つまり、ログがどんどん肥大化していくので、サンプル欄には載せてませんが一定行以上でログファイルを空にするスクリプトを追加してあります。ご了承ください。

サンプルスクリプト

  1. <?php
  2. $filepath = 'locktest.txt'; // ログファイルへのパス
  3. $lockfile = 'lock.txt'; // ロック専用ファイルへのパス
  4.  
  5. // ロック専用ファイルを開く 中身は空で構わない
  6. $lock_fp = fopen($lockfile,"w");
  7. flock($lock_fp,LOCK_EX); // ロック用ファイルにロック
  8.  
  9. // 元のファイル内容を変数に格納しておく
  10. $old_log = file_get_contents($filepath);
  11.  
  12. //配列に格納しておいたので・・・・
  13. $fp = fopen($filepath,"w"); // wモードでログファイルの中身を空にして開く
  14.  
  15. fwrite($fp, "この行が追加されます\n"); // 追加する最新行を書き込む
  16. fwrite($fp, $old_log); // 変数に格納しておいた古いログを書き込む
  17.  
  18. fclose($fp); // ログファイルを閉じる
  19.  
  20. $lines = file($filepath); // 新しくなったログを読み込みむ
  21.  
  22. fclose($lock_fp); // ロック用ファイルを閉じてファイルポインタを破棄
  23.  
  24. //=====================================
  25. // ここから出力用の処理
  26. //=====================================
  27. $i = 1; // 行数カウント用
  28. foreach($lines as $value){
  29.   // ファイル内容を行番号付きで改行しながら出力
  30.   echo $i."".$value."<br />";
  31.   $i++; // 行数カウントアップ
  32. };
  33. ?>

出力結果

1:この行が追加されます
2:この行が追加されます
3:この行が追加されます
4:この行が追加されます
5:この行が追加されます
6:この行が追加されます
7:この行が追加されます
8:この行が追加されます

参考関数

  • fopen() ---- ファイルまたはURLを開く
  • flock() ---- ファイルをロックする
  • file_get_contents() ---- ファイルの内容を全て取得する
  • fwrite() ---- ファイルをバイナリ・モードで書き込む
  • fclose() ---- ファイルポインタを閉じる
  • file() ---- ファイルの内容を全て取得して配列に格納する

使ってる関数は全て今まで紹介してきた関数です。最後の$iは行数を出力するためにつけてるだけですし、その他は「ロック専用ファイルを使ってロックする」というだけで普通の処理と変わりません。ログファイルを開く時のfopen()にはロックをかけなくていいの?と思われるかもしれませんが、かけなくていいです。本来、排他ロック中には他のプロセスが割り込めないのでロックは1つかければ十分なのです。

また、一連の処理の中でロックをかけたり外したりするのはプロセスに割り込ませるスキを与えるだけです。このスクリプトの場合、一連の処理というのは「最初のファイルの読み込みから最後のファイルの読み込み」までです。どうしてかは、データの整合性を保たなければならないのはどこからどこまでか?と考えると分かると思います。

補足ですが、排他制御の「ロック専用ファイル」には今回のようにそのファイルそのものにロックをかけるのではなく、「ロック専用ファイルの存在をチェックして、ファイルが存在すれば処理を待つ、存在しなければロックファイルを作成して処理を続行、ロック開放時にはファイルを消去する」というような使い方もあります。これはflock()関数が使えない場合などに有効ですが、この手法を使う場合もいくつか注意点があって、flock()使うよりややこしいです。ここでは詳しく解説しませんが、flock()関数が使えればそちらを使うのが楽でいいでしょう。