Python で書き込むためにファイルをロックする必要があります。一度に複数の Python プロセスからアクセスされます。オンラインでいくつかの解決策を見つけましたが、多くの場合、Unix ベースまたは Windows ベースのみであるため、ほとんどの場合、私の目的には失敗します。
14 に答える
よし、ここに書いたコードで終わった.私のウェブサイトのリンクは死んでいる.archive.orgで見る(GitHubでも利用可能)次の方法で使用できます。
from filelock import FileLock
with FileLock("myfile.txt"):
# work with the file as it is now locked
print("Lock acquired.")
他のソリューションでは、多くの外部コード ベースが引用されています。自分でやりたい場合は、Linux / DOS システムでそれぞれのファイル ロック ツールを使用するクロスプラットフォーム ソリューションのコードを次に示します。
try:
# Posix based file locking (Linux, Ubuntu, MacOS, etc.)
# Only allows locking on writable files, might cause
# strange results for reading.
import fcntl, os
def lock_file(f):
if f.writable(): fcntl.lockf(f, fcntl.LOCK_EX)
def unlock_file(f):
if f.writable(): fcntl.lockf(f, fcntl.LOCK_UN)
except ModuleNotFoundError:
# Windows file locking
import msvcrt, os
def file_size(f):
return os.path.getsize( os.path.realpath(f.name) )
def lock_file(f):
msvcrt.locking(f.fileno(), msvcrt.LK_RLCK, file_size(f))
def unlock_file(f):
msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, file_size(f))
# Class for ensuring that all file operations are atomic, treat
# initialization like a standard call to 'open' that happens to be atomic.
# This file opener *must* be used in a "with" block.
class AtomicOpen:
# Open the file with arguments provided by user. Then acquire
# a lock on that file object (WARNING: Advisory locking).
def __init__(self, path, *args, **kwargs):
# Open the file and acquire a lock on the file before operating
self.file = open(path,*args, **kwargs)
# Lock the opened file
lock_file(self.file)
# Return the opened file object (knowing a lock has been obtained).
def __enter__(self, *args, **kwargs): return self.file
# Unlock the file and close the file object.
def __exit__(self, exc_type=None, exc_value=None, traceback=None):
# Flush to make sure all buffered contents are written to file.
self.file.flush()
os.fsync(self.file.fileno())
# Release the lock on the file.
unlock_file(self.file)
self.file.close()
# Handle exceptions that may have come up during execution, by
# default any exceptions are raised to the user.
if (exc_type != None): return False
else: return True
通常はステートメントを使用AtomicOpen
するブロックで使用できるようになりました。with
open
警告:
- exitが呼び出される前に Windows と Python で実行するとクラッシュした場合、ロックの動作がどうなるかわかりません。
- ここで提供されるロックは、絶対的なものではなく、助言的なものです。競合する可能性のあるすべてのプロセスは、「AtomicOpen」クラスを使用する必要があります。
- (2020 年 11 月 9 日) 現在、このコードは Posix システム上の書き込み可能なファイルのみをロックします。投稿後、この日付より前のある時点で、
fcntl.lock
読み取り専用ファイルで を使用することが違法になりました。
私はそれを行うためのいくつかのソリューションを検討してきましたが、私の選択は oslo.concurrency です
それは強力で、比較的よく文書化されています。それは留め具に基づいています。
その他の解決策:
- Portalocker : exe インストールである pywin32 が必要なため、pip 経由では実行できません
- ファスナー: 文書化が不十分
- lockfile : 非推奨
- Flufl.lock : POSIX システム用の NFS セーフ ファイル ロック。
- simpleflock : 最終更新 2013 年 7 月
- zc.lockfile : 2016 年 6 月の最終更新 (2017 年 3 月現在)
- lock_file : 2007 年 10 月の最終更新
私はlockfileを好みます—プラットフォームに依存しないファイルロック
ロックはプラットフォームとデバイスに固有ですが、一般的にいくつかのオプションがあります。
- flock() または同等のものを使用します (OS がサポートしている場合)。これはアドバイザリー ロックです。ロックを確認しない限り、無視されます。
- ファイルをコピーし、新しいデータを書き込んでから移動します (コピーではなく移動 - Linux では移動はアトミック操作です - OS を確認してください)。ロックファイルの存在。
- ディレクトリを「ロック」として使用します。NFS は flock() をサポートしていないため、NFS に書き込む場合はこれが必要です。
- プロセス間で共有メモリを使用する可能性もありますが、試したことはありません。それは非常にOS固有です。
これらすべての方法では、ロックの取得とテストにスピンロック (失敗後の再試行) 手法を使用する必要があります。これにより、同期ミスの小さなウィンドウが残りますが、一般的には大きな問題にならないほど小さいです。
クロスプラットフォームのソリューションを探している場合は、他のメカニズムを介して別のシステムにログインすることをお勧めします (次善の方法は、上記の NFS 手法です)。
sqlite は、NFS を介して通常のファイルと同じ制約を受けることに注意してください。そのため、ネットワーク共有上の sqlite データベースに書き込み、無料で同期を取得することはできません。
ファイルのロックは通常、プラットフォーム固有の操作であるため、異なるオペレーティング システムで実行できるようにする必要がある場合があります。例えば:
import os
def my_lock(f):
if os.name == "posix":
# Unix or OS X specific locking here
elif os.name == "nt":
# Windows specific locking here
else:
print "Unknown operating system, lock unavailable"
私は、同じディレクトリ/フォルダー内から同じプログラムの複数のコピーを実行し、エラーをログに記録する、このような状況に取り組んできました。私のアプローチは、ログ ファイルを開く前にディスクに「ロック ファイル」を書き込むことでした。プログラムは、先に進む前に「ロック ファイル」の存在をチェックし、「ロック ファイル」が存在する場合はその順番を待ちます。
コードは次のとおりです。
def errlogger(error):
while True:
if not exists('errloglock'):
lock = open('errloglock', 'w')
if exists('errorlog'): log = open('errorlog', 'a')
else: log = open('errorlog', 'w')
log.write(str(datetime.utcnow())[0:-7] + ' ' + error + '\n')
log.close()
remove('errloglock')
return
else:
check = stat('errloglock')
if time() - check.st_ctime > 0.01: remove('errloglock')
print('waiting my turn')
編集--- 上記の古いロックに関するいくつかのコメントを検討した後、コードを編集して、「ロック ファイル」の古さのチェックを追加しました。私のシステムでこの関数を数千回繰り返すと、直前から平均 0.002066... 秒になりました。
lock = open('errloglock', 'w')
直後に:
remove('errloglock')
そのため、古さを示し、問題の状況を監視するために、その量の 5 倍から始めることにしました。
また、タイミングを調整していると、実際には必要のないコードが少しあることに気付きました。
lock.close()
これは open ステートメントの直後にあったため、この編集で削除しました。
pylocker は非常に便利です。ファイルのロックやロック メカニズム全般に使用でき、複数の Python プロセスから一度にアクセスできます。
単にファイルをロックしたい場合は、次のように機能します。
import uuid
from pylocker import Locker
# create a unique lock pass. This can be any string.
lpass = str(uuid.uuid1())
# create locker instance.
FL = Locker(filePath='myfile.txt', lockPass=lpass, mode='w')
# aquire the lock
with FL as r:
# get the result
acquired, code, fd = r
# check if aquired.
if fd is not None:
print fd
fd.write("I have succesfuly aquired the lock !")
# no need to release anything or to close the file descriptor,
# with statement takes care of that. let's print fd and verify that.
print fd
grizzled-python からのシンプルで機能する (!)実装を見つけました。
単純な使用 os.open(..., O_EXCL) + os.close() は、Windows では機能しませんでした。