マルチプロセス環境において、複数のプロセスがよってたかって1つのファイルなどのリソースにアクセスするのを避けるために、プロセス間で排他ロックしたいときに使えるライブラリfastenersを試してみました。
環境はMac & Python 3.7.3 & fasteners 0.15です。
インストール
pipとか、仮想環境ならpipenvとかでインストールします。
1 2 | pip install fasteners # pipでインストール pipenv install fasteners # pipenvで仮想環境へのインストール |
サンプルプログラム
その1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import fasteners import time import datetime def main(): lock = fasteners.InterProcessLock('lockFile') # 排他ロックをかける # もし他のプロセスがロックをかけていたら、開放されるまで待つ。 lock.acquire() try: print('Start:', datetime.datetime.now()) # 処理開始時間表示 time.sleep(5) # 排他ロックしたい処理(このデモでは5秒間スリープするだけ) print('End :', datetime.datetime.now()) # 処理終了時間表示 finally: lock.release() # ロックを解放 if __name__ == '__main__': main() |
7行目: lock = fasteners.InterProcessLock('locckFile')
ロックファイルを指定します。
リソースを共有する複数のプロセス同士は、このロックファイルの状態を通じて状態を把握します。
11行目: lock.acquire()
排他ロックを試みます。
排他ロックに成功すればそれ以降のプログラムが実行されます。ロックに失敗した場合は、別プロセスがロックを解放し、順番が回ってくるまでプログラムの実行がブロックされます。
18行目: lock.release()
ロックを開放します。開放し忘れるとデッドロックが発生するので要注意。
ロック中に実行したプログラムにエラーが発生しても、try-finallyのfinallyの中に書いておくと確実に実行されるので安心です。
14〜16行目:
ロック中に実行するプログラムです。
5秒間のスリープと、その前後の時刻を表示します。
サンプルの実行してみます。
ターミナルを2つ起動して、まず1つめのターミナルでサンプルプログラムを実行し、その直後2つめのターミナルで同プログラムを実行します。
手動で行ったのでタイム差は2秒ぐらいでしょうか。
1つめのターミナルの実行結果
1 2 | Start: 2019-06-11 20:31:07.027808 End : 2019-06-11 20:31:12.032832 |
2つめのターミナルでの実行結果
1 2 | Start: 2019-06-11 20:31:12.115784 End : 2019-06-11 20:31:17.120858 |
ターミナル1のプロセスが20:31:07にロック、5秒後の10:31:12ロック解除。
続けてターミナル2のプロセスが20:31:12にロック、5秒後の20:31:17にロック解除。
ターミナル1、ターミナル2の順番に、時間が重なり合わずにプログラムが動作しています。排他ロックがちゃんと働いていたということです。
その2
このように、withを使った記述も出来ます。
この記述方法は、acqire()とrelease()が不要で、withのブロック範囲が自動で排他ロックされます。
ロックの解除忘れ防止も出来て便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import fasteners import time import datetime def main(): # 排他ロックをかける(require(), release()不要) # もし他のプロセスがロックをかけていたら、開放されるまで待つ。 with fasteners.InterProcessLock('lockFile'): print('Start:', datetime.datetime.now()) # 処理開始時間表示 time.sleep(5) # 排他ロックしたい処理(このデモでは5秒間スリープするだけ) print('End :', datetime.datetime.now()) # 処理終了時間表示 if __name__ == '__main__': main() |
その3
デコレータを使って、関数まるごと排他ロックの範囲に出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import fasteners import time import datetime # 関数まるごと排他ロックをかける(require(), release()不要) # もし他のプロセスがロックをかけていたら、開放されるまで待つ。 @fasteners.interprocess_locked('lockFile') def main(): print('Start:', datetime.datetime.now()) # 処理開始時間表示 time.sleep(5) # 排他ロックしたい処理(このデモでは5秒間スリープするだけ) print('End :', datetime.datetime.now()) # 処理終了時間表示 if __name__ == '__main__': main() |
以上です。
参考にしたサイト