Pythonのfcntl.flockについて
これはWindowsでは使えないことになってます。
使えたとしてもDockerが必須条件みたいです。
ただ僕はDockerを使ってません。
でも排他的ファイルロックが必要でした。
その回避策というか方法についてまとめます。
このページの目次
Windowsでは No module named 'fcntl' エラーが出る
例えばこういうコードを書いてみたとします。
▼ fcntl.flockによる多重プロセス禁止のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import fcntl def main(): with open('test.lock','a+') as oLockFile: try: fcntl.flock( oLockFile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB ) except IOError: return if __name__ == '__main__': main() |
▼ このようなエラーが出てしまう...
1 2 3 4 |
Traceback (most recent call last): File "test.py", line 15, in <module> import fcntl ModuleNotFoundError: No module named 'fcntl' |
システム的な問題なので仕方ない
Pythonはともかく、PHPなどではflockはマルチプラットでWindowsでも使えるんですよね。それと同じ感覚で使えると思ってたから困りました。(参考記事 : PHPスクリプトの多重起動を防止するには【flockによる方法】)
portalocker.pyというスクリプトを使ってみた
同じように悩んでいる人は多いみたいです。
そこでこんなスクリプトを配布してる記事を見つけます。
ただPython2なのでPython3では修正が必要です。
僕の場合は次のようにスクリプトを使いました。
手始めとして pywin32 をインストール
まずは pywin32 をインストールします。
▼ Anaconda・MiniCondaを使っていない場合
1 |
pip install pywin32 |
▼ Anaconda・Minicondaで環境構築してる場合
1 |
conda install pywin32 |
これでWindows依存の機能が使えるようになります。
各自環境に portalocker.py を追加する
先ほど紹介した次のページに載ってるコードです
URL : https://code.activestate.com/recipes/65203/
ただPython3では構文の関係からそのままでは動きません。
もしエラーが出るならPython3向けに少し修正してください。
▼ 修正した portalocker.py のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
__all__ = [ "lock", "unlock", "LOCK_EX", "LOCK_SH", "LOCK_NB", "LockException", ] import os class LockException(Exception): # Error codes: LOCK_FAILED = 1 if os.name == 'nt': import win32con import win32file import pywintypes LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK LOCK_SH = 0 # the default LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY __overlapped = pywintypes.OVERLAPPED() elif os.name == 'posix': import fcntl LOCK_EX = fcntl.LOCK_EX LOCK_SH = fcntl.LOCK_SH LOCK_NB = fcntl.LOCK_NB else: raise RuntimeError#, "PortaLocker only defined for nt and posix platforms" if os.name == 'nt': def lock(file, flags): hfile = win32file._get_osfhandle(file.fileno()) try: win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped) except pywintypes.error as exc_value: raise IOError def unlock(file): hfile = win32file._get_osfhandle(file.fileno()) try: win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) except pywintypes.error as exc_value: if exc_value[0] == 158: pass else: raise elif os.name == 'posix': def lock(file, flags): try: fcntl.flock(file.fileno(), flags) except IOError as exc_value: raise IOError def unlock(file): fcntl.flock(file.fileno(), fcntl.LOCK_UN) |
恐らく try...except... の箇所さえ直せばOKなはず。
少なくとも僕の環境では少しの手直しで動かせました。
排他的ロックをportalocker.lockで書いたコード例
こういうコードを試しに書いてみました。
▼ 排他的ロックによる多重起動禁止
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import portalocker def main(): lockfile = open('test.lock', "a+") try: portalocker.lock(lockfile, portalocker.LOCK_EX | portalocker.LOCK_NB) except IOError: # 既に起動済み print('Already running!') return # 何か処理... |
まずロックファイルをオープン
そのあと portalocker.lock() に対して fcntl.flock() と同じような引数で使えます。2番目の引数に portalocker.LOCK_NB を付与するとロック中にプロセスをブロックしません。
そしてロック中なら IOError が発生するので、発生してたならプロセス起動中。そうでないならプロセス続行のような処理もできます。
以上、fcntl.flockをマルチプラットに使う方法でした。ではまた