🔆

Linux上のPythonで二重起動を禁止する

2023/07/16に公開

はじめに

同じプログラムを同時に起動されると困ることは多々あるので、それを防止するコードです。
Linuxのシステムコールのflockを使うよくある仕組みのPython版といったところで、何かと便利なので自分もよく使っています。

同時実行を禁止する場合、エラーを表示して終了したいケースと、他のプロセスの終了を待って実行したいケースの2パターンがあるので、それぞれを載せています(差異はわずかですが)。

注意事項
Linuxにおけるファイルアクセス管理の機能を使います。Windowsの場合は、このコードでは実行できません。

エラーを表示して終了するケース

import fcntl

def main():
    pass  # 任意の処理

if __name__ == '__main__':
    with open('lockfile', 'w') as f:
        try:
            fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError:
            print('Another instance is running')
            exit(0)
        try:
            main()
        finally:
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)

lockfileの名前は好きに変えてください。このコードではカレントディレクトリにlockfileという名前のファイルが作成されてしまうので、例えば/tmp以下に配置するなど工夫をするといいと思います。

簡単に解説をすると、fcntl.LOCK_EXは排他ロックの獲得、fcntl.LOCK_NBはノンブロッキングです。まずは排他ロックの獲得を試みて、失敗した場合は待機せずにIOErrorが発生するので、それをキャッチしてプログラムを終了させています。
排他ロックを獲得できた場合はmain()を実行し、最後にfcntl.LOCK_UNで排他ロックを解除しています。

他のプロセスが終了するまで待機するケース

import fcntl

def main():
    pass  # 任意の処理

if __name__ == '__main__':
    with open('lockfile', 'w') as f:
        try:
            fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError:
            print('Another instance is running')
            fcntl.flock(f.fileno(), fcntl.LOCK_EX)  # ここだけ変更
        try:
            main()
        finally:
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)

ブロッキングあり(つまりfcntl.LOCK_NBなし)で排他ロックを獲得をするコードを追記することで、ロックを獲得できるまで待機してくれるようになります。ロック獲得後は、通常通りmain()が呼ばれます。

補足
経験上の話ですが、待機プログラムが複数ある場合は、先に待機していた方のプログラムから実行されます。つまり、キュー(Queue)として動作します。
ただしそれが仕様なのかはわからず、環境によって違うかもしれないので、参考程度にとどめておいてください。

Discussion