2006-10-06
● [プログラミング] C++でロック(flock, lockf, fcntl)
昨日の日記でロックのことを書いたら、その後Googleでflockなどを調べてここに辿り着く人もいるようなので、一応ソースも掲出。
C++ではファイルの入出力を主にfstreamで行うことが想定されているものの、fstreamとそのバッファクラスのfilebufにはファイルのロック機構は用意されていません。
flock などの関数はC言語では比較的低レベルな関数でシステムコールに近く、純粋な(理想的な)言語体系を求めたC++にはそぐわなかったためと思われます。ただ、filebufからも同様の理由でファイルデスクリプタやFILE構造体のポインタを取得することができず、これらを引数とするflockを行うことができません。
といった事情から、C++でロック機構を行うためには、
- 独自にfilebufを拡張する
- ロックファイルを別途用意する
といった処理が必要となります。
工程数やデバッグのしやすさから言うと後者のほうがはるかに楽なので、仕事上は後者の方法を使っています。一応ソースだけ出しておきます:
lockfile.cpp
/* Traditional C headers */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
/* C++ header */
#include <string> // for filename member
/* My header */
#include "lockfile.hpp"
namespace fileutil {
void lockfile::reset()
{
if (fd) {
close(fd);
fd = 0;
}
m_flock.l_type = F_WRLCK;
m_flock.l_start = 0;
m_flock.l_whence = SEEK_SET;
m_flock.l_len = 0;
m_flock.l_pid = getpid();
}
bool lockfile::lock()
{
if (fd != 0)
return false;
if (filename.length() == 0)
return false;
fd = open(filename.c_str(), O_CREAT|O_WRONLY|O_NONBLOCK, 0644);
if (fd < 0)
return false;
return (fcntl(fd, F_SETLKW, &m_flock) != -1);
}
bool lockfile::unlock()
{
if (fd == 0)
return false;
close(fd);
unlink(filename.c_str());
reset();
return true;
}
} // endof namespace fileutil
lockfile.hpp
#ifndef LOCKFILE_HPP
#define LOCKFILE_HPP
/* Traditional C headers */
#include <fcntl.h>
/* C++ header */
#include <string>
namespace fileutil {
/* class definition */
class lockfile
{
public:
bool lock();
bool unlock();
lockfile() { reset(); }
lockfile(std::string fname) { reset(); filename = fname; }
~lockfile() { if (fd != 0) unlock(); }
std::string filename;
private:
int fd;
struct flock m_flock;
void reset();
};
} // namespace fileutil
#endif /* LOCKFILE_HPP */
こんな風に使います:
#include <iostream>
#include "lockfile.hpp"
int main(int argc, char** argv)
{
fileutil::lockfile lck("/mnt/some_nfsdev/lockfile");
std::cout << "Waiting for lock..." << std::endl;
lck.lock();
std::cout << "Lock is set." << std::endl;
/* DO SOMETHING */
sleep(5);
lck.unlock();
std::cout << "Lock is unset." << std::endl;
}
本当は、ロックファイルがすでにある場合にロックするかどうかを判断させるような仕組みがあったほうがよいのかもしれませんが、とりあえず自分で使う分にはこれで十分だったので。一応。
(追記)
さて、fstream側を拡張させる方法についても簡単に。というわけでGCCのソースを引っ張り出す。
GCCでは、移植性を保つために、システムコールに近い部分になればなるほど、幾重にもラッパ関数やらテンプレートやらで隠蔽されてて、複数のクラスが出てきてちょっとイライラします。
で、とりあえず出力ストリームにロックをかけるには何らかの形で ファイルデスクリプタを取り出す必要があるので、そういった観点でソースを紐解いて今のところわかったこと:
/usr/include/c++/3.3/fstream:
- ofstream のテンプレートクラスは basic_ofstream。
- basic_ofstream のプライベートメンバ変数に _M_filebuf (型は basic_filebuf<>) があって、パブリックメソッドの rdbuf() でこいつのポインタを呼び出せる
- basic_filebuf は、プロテクテッドな __basic_file クラスのメンバ変数 _M_file を持つ (フレンドクラスは ios_base)。
./libstdc++-v3/config/io/basic_file_libio.h:
- __basic_file 型はパブリックメソッド fd() を持っていて、こいつがファイルデスクリプタを返すっぽい (宣言だけあって定義がない??)
./libstdc++-v3/libio/libio.h:
- __basic_file は構造体 _IO_FILE を継承したクラスで、_IO_FILE には _fileno という整数型のメンバ変数 (FDっぽい) がある。
というわけで、ofstream からファイルデスクリプタを取り出すには、
ofstream.rdbuf()->_M_file.fd(); ofstream.rdbuf()->_M_file._fileno;
としてやればよさそうだけど、しかし _M_file がプライベートでアクセスメソッドもないのでアクセスできない。basic_filebuf を継承して _M_file へのアクセスメソッドを用意する?? でもそれだとbasic_[io]fstreamも作り直すはめに。それなら最初っから作り直して lock メソッドを用意した方がよさそう??
つmutex
んー、最初はmutex使えるのかなぁと思ったんですけどね。でもこれって別々のアプリケーション間での排他制御ってできないのでは?? (試し方が悪かったんだろうか)<br><br>あと、こちらは不勉強なのですが、mutexって例外やSEGVで落ちた時の振る舞いはどうなるんだろうとか、別ホストで動作しているアプリケーション(スレッド)がNFS上のファイルを読みに言行ったときでも排他制御できるのか、とか…不安が多いもので(じゃぁ試せよって話になるので、試してみたんですが試し方が悪いのかなぁ)
なお、このlockfile.cppもバグ(?)があって、それはclose()してからunlink()するまでの間が不安だったり。まぁ本当にfsレベルでちゃんとロックがかかってれば大丈夫だとは思いますが。