ログ出力などしようと思えばファイルのロックをしたくなります。


この記事は、ログ出力を実装する上での注意点とは? の続きです。


ファイルのロックは難しい問題です。

ここでは、Boostのfile_lockを使用したロックを実現してみます。



【準備】

まず、boostのfile_lockクラスは、コンパイルの必要がありません。

しかし、ちょっとバグがあります。

修正方法をまとめましたので、参考にして修正してみてください。

 boostのfile_lockのコンパイルエラーの回避方法



【サンプル】

#include <cstdlib>
#include <iostream>
#include <string>
#include <fstream>
#include <stdexcept>



#include <boost/interprocess/sync/file_lock.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>


using namespace std;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::interprocess;


int main()
{
    //This throws if the file does not exist or it can't
    //open it with read-write access!


    //ロックしたファイルは書き込みできないのでロック用のファイルを書き込みと別に作成。
     ofstream ofs_t("_lock_myfile.txt", ios_base::app);
     ofstream ofs("myfile.txt", ios_base::app);


    try{
       /*

       コンストラクタではロックされません。
       OSにファイルロック機構があるか?ファイルは存在するか?のみチェックします。

       */
        interprocess::file_lock flock("_lock_myfile.txt");


      //ファイルロックできるまでに待つ時間を5秒にします。
       ptime pt = second_clock::universal_time();
       pt += seconds(5);

     //ファイルロックを試みます。5秒以内にロックできなければ例外を発生させます。
      if(!flock.timed_lock(pt)) throw runtime_error("lock is failed.");

      ofs << "fasfas" << endl;

      system("PAUSE");


    }catch(exception& e){
      cout << e.what() << endl;
    }

    system("PAUSE");

    return 0;
}



【実験】

以下のように実験してみてください。

サンプルでは、ロックを掛けたままPAUSEするようにしています。

ですので、exeを実行したら、もう一回exeを実行してください。

2回目の実行では、すぐにPAUSEされません。

それは、1回目のexe実行のロックを解除されるのを待っているのです。

待っている時間は5秒です。

5秒経ったら勝手に終了します。

そうしたら、何かキーを押して1回目の実行プロンプトを閉じてください。


そして、ファイル「myfile.txt」を開いてみてください。

2回exeを実行しているので、2行のデータが出力されるはずですが、2回目がロックされて出力できないので

1行しか出力されていません!


ロックが確認できましたよね!(^^)



【説明】

まず最初に、予想外の動きをしていたことを書いておきます。


上のサンプルを見ていただくと分かると思いますが、ファイルロックしているのは書き込みファイル「myfile.txt」ではなく、「_lock_myfile.txt」です。

なぜかというと、理由は分からないのですが、ロックしたファイルを書き込みできなかったからです。

これはバグなのか、Windowsの仕様なのかは分かりません。

ですので、ロック用のファイルを作成して、それをロックしました。


これでもファイルに同時に書き込まれることがないのは分かるでしょうか?

プログラムがみんな同じファイルをロックできるまで待っているので、

ファイルに書き込むプログラム以外はロック解除されるまで待っているのです。

こういうことも良くやることです。


さて、ではロックを実施するクラスの説明に移ろうと思います。



<コンストラクタ>

interprocess::file_lock flock("_lock_myfile.txt");


クラスを作成します。

コンストラクタはファイルの存在チェックと、ロック機構があるか?をチェックしているようです。

コンストラクタを起動するだけではファイルはロックされません。

注意点としては、ファイル存在チェック、ロック機構存在チェックでエラーになると

interprocess_exception 例外が発生します。

これは、exceptionから派生している例外クラスです。



<ptimeについて>

これは、時間を扱う汎用的なクラスで、ファイルロックと直接関係の無いクラスです。

POSIXを利用しているようです。簡単に時間の加減算ができます。

すみませんが、ここではあまり深く説明しません。



<timed_lock()関数>

if(!flock.timed_lock(pt)) throw runtime_error("lock is failed.");


ロックを掛け、所有権を取得する関数です。

他の誰かがロックを先に掛けている場合は、ロックが解除されるまで待ちます。

引数に時間を渡すことができ、指定の時刻までにロックが取得できないとロックをあきらめます。

引数の型はptimeです。

返り値は、成功時にtrueとなります。

何かエラーが発生した場合は、interprocess_exceptionが投げられます。



<lock()関数>

ここでは使用していませんが、もう一つのロックを掛ける関数です。

ただし、他の誰かが先にロックを掛けていた場合、その人がロックをはずすまで待ち続けます。

ちょっとけなげなロックです。

何かエラーが発生した場合は、interprocess_exceptionが投げられます。



<unlock()関数>

明示的にロックを解除する関数です。

ちなみにクラスが破壊されるときに自動でロック解除されます。




【補足】

このBoostのfile_lockクラスは、POSIXとWindowsの環境で使用できるようです。

2つの間のファイルロックなどのプログラミングの仕方は違うのですが、それらの違いを吸収したのが

file_lockクラスです。

使い方は吸収できましたが、どうも、それぞれの動作機構などまで吸収できなかったようです。

そのため、使用するときは以下のことを守って欲しいようです。


①1つのプロセスについて、file_lockオブジェクトの使用はファイル1つにつき1つだけにしてください。

②同じスレッドが、ロックしたファイルをロック解除するようにしてください。

③std::fstreamを使用してロックしたファイルに書き込む場合、そのファイルのロックをすべて解放するまで

 ファイルのクローズをしないでください。



この説明 のTo sum upを和訳しただけですが、英語はさっぱりなので間違っていたらすみません。あせる

正確には英語の説明を参照ください。


 この内容が事実とすると、WEB用のログ出力には使用できないことになります。

 なぜなら、Apacheなどは1つのプロセスを起動してそこから複数のスレッドを作り、

 CGIを実行するからです。

 つまり、①に反することになります。



【おまけ】

英語だれか訳してくれるとうれしいなー。

というわけで、いろいろ頑張ってコマ送り的な読み方をすると、

OSによっては、ファイルのある位置からある位置までをロックする、といったことができるようです。

今のバージョン(boost1.35)では実装しませんでしたが、Comming Soon的なことが書かれているようです。

ほんとかなはてなマーク



日本語で説明したサイトや文献が全然なく、この記事の内容は少し不安です。

すみませんが正確な情報はBoostのサイトで取得をお願いします。

もうしわけないですしょぼん



参考:

・ログ出力を実装する上での注意点とは?

・ログ出力クラスを作成するには?  

boostのfile_lockのコンパイルエラーの回避方法

・Boostのfile_lockクラスの和訳メモ

Boostのファイルロックの説明

Boostのfile_lockのリファレンス
Apacheとプロセスについてちょっと書いてある記事