Python: ファイルロックによる排他制御(fcntl編)

2010年2月14日

PythonでCGI実装していると、CGIからいろんな資源にアクセスすることになります。


RDBとかファイルとか。。。


RDBに対するR/Wは、RDB管理モジュールが同時書き込みに対する排他制御を行ってくれるので 何も意識することはありませんが、ファイルに対するR/Wとなるとそうはいきません。

ファイルで特に問題になるのは、書き込みです。

ファイルは複数プロセスで同時にオープンすることができますので、 あるプロセスで書き込みを行っている最中に別のプロセスが書き込むことができます。 しかしファイルの実態は一つなので、タイミングによってはどちらかの書き込みの内容が 全く反映されない。。。なんてことも起こりえます。
これは、書き込みプロセスと読み込みプロセス間でも同様の問題が発生します。

こんなときに使うのがプロセス間の排他制御です。
今回紹介するのは、 fcntlモジュールです。 本モジュールはUnix系のシステムコールflock()を内部で呼ぶのでUnix限定となります。

基本的なロック・アンロックは以下のように実現します。

1
2
3
4
5
6
import fcntl
ifp = file("hoge.txt", "r")
fcntl.flock(ifp.fileno(), fcntl.LOCK_EX)        # 排他ロック獲得 
# fcntl.flock(ifp.fileno(), fcntl.LOCK_SH)    # 共有ロック獲得 
fcntl.flock(ifp.fileno(), fcntl.LOCK_UN)        # 排他ロック解放 
ifp.close()

ロックの種別は2つで、共有ロックと排他ロックがあります。


共有ロック(fcntl.LOCK_SH)は、複数プロセスでロックを獲得することができるもので、 主に読み込みしかしない処理区間で使用します。 実際、共有ロックは"r" or "r+"でオープンしたファイルディスクリプタに対してしか 使用できません。

大事なのは、共有ロック獲得中でも他プロセスは共有ロックを獲得できるが、 排他ロックとして獲得にきたプロセスはブロックされるということです。

つまり、誰か一人でも このファイルをリードしている場合は、書き込み側である排他ロックを獲得しようとしているプロセスは ブロックされるということです。

次は排他ロック(fcntl.LOCK_EX)です。
排他ロックはその名の通り、誰か一人でも排他ロックを獲得したら他プロセスからのロック 獲得はブロックされるというものです。このロックは、R/Wのどちらのモードでオープンしてもロックできます。 基本的には、書き込み区間で使用します。

上記のように読み込みプロセスと書き込みプロセスがいる場合、2つのロックを理解して使用しないと効率が 大変悪くなります。例えば、すべてのプロセスで排他ロックだけを使用していると、 読み込みだけの区間なのに、読み込みプロセスが排他ロックを獲得している間は、他の読み込みプロセスも ブロックされることになります。


勉強不足がこのような非効率実装を生むことになるので、気をつけましょう。

ロックを解放するには、fcntl.LOCK_UNを使用します。
これは、共有ロック・排他ロックともに共通です。

最後に、上記のようなロックでブロックされる場合の挙動についてですが、 ロックを獲得できなかったプロセスは、上記実装ではスリープします。 つまり、プロセスの処理が停止して、他プロセスに処理が移ることを意味します。


その後誰かがロックを解放し順番がくるとスリープ状態から起こされ、ロックを獲得し、処理が継続します。


これをmutex lockと言います。 一方で、flockのフラグにfcntl.LOCK_NBを併用すると、 ロックが獲得できなかったときにスリープせずに処理を継続する、いわゆるtry lock を実現できます。 違いは、ロックが獲得できなかったときのリトライ処理を自分で定義できるか否かです。

ファイルロックは複雑です。いろいろと調べて使ってみてください。