Pythonでファイルをロックする


152

Pythonで書き込むためにファイルをロックする必要があります。一度に複数のPythonプロセスからアクセスされます。オンラインでいくつかの解決策を見つけましたが、それらは多くの場合UnixベースまたはWindowsベースのみであるため、ほとんどの場合私の目的に失敗します。

回答:


115

さて、私がここで書いたコードを書き終えました。私のウェブサイトのリンクは死んでいます。archive.orgで表示してくださいGitHubでも入手可能)。次の方法で使用できます。

from filelock import FileLock

with FileLock("myfile.txt.lock"):
    print("Lock acquired.")
    with open("myfile.txt"):
        # work with the file as it is now locked

10
ブログ投稿のコメントにあるように、このソリューションは「完全」ではありません。ロックがそのままにされ、ファイルの前に手動でロックを削除する必要があるような方法でプログラムが終了する可能性があるためです。再びアクセス可能になります。しかし、それはさておき、これはまだ良い解決策です。
leetNightshade

3
しかし、エヴァンのFileLockの別の改良版は、ここで見つけることができます:github.com/ilastik/lazyflow/blob/master/lazyflow/utility/...
スチュアートベルク

3
OpenStackは、独自の(まあ、Skip Montanaroの)実装を公開しました-pylockfile-以前のコメントで言及されたものと非常に似ていますが、一見の価値があります。
jweyrich、2014

7
@jweyrich Openstacks pylockfileは非推奨になりました。代わりに、ファスナーまたはoslo.concurrencyを使用することをお勧めします。
harbun

2
別の同様の実装では、私は推測する:github.com/benediktschmitt/py-filelock
herry

39

ここにクロスプラットフォームのファイルロックモジュールがあります:Portalocker

ケビンが言うように、一度に複数のプロセスからファイルに書き込むことは、可能であれば避けたいものです。

問題をデータベースに詰め込むことができれば、SQLiteを使用できます。同時アクセスをサポートし、独自のロックを処理します。


16
+1-SQLiteは、ほとんどの場合、このような状況に対応する方法です。
cdleary 2009年

2
その上、PortalockerにはWindows用のPython拡張機能が必要です。
n611x007 2013

2
@naxaのみMSVCRTとのctypesに依存していることの変種があり、見るroundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/...
Shmilザ・キャット

@ n611x007 Portalockerが更新されたため、Windowsの拡張機能は不要になりました:)
Wolph

2
SQLiteは同時アクセスをサポートしていますか?
piotr

23

他のソリューションは多くの外部コードベースを引用しています。自分で実行する場合は、Linux / DOSシステムでそれぞれのファイルロックツールを使用するクロスプラットフォームソリューションのコードを次に示します。

try:
    # Posix based file locking (Linux, Ubuntu, MacOS, etc.)
    import fcntl, os
    def lock_file(f):
        fcntl.lockf(f, fcntl.LOCK_EX)
    def unlock_file(f):
        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使用できます。withopen

警告: Windowsで実行していて、exitが呼び出される前にPythonがクラッシュした場合、ロックの動作がどうなるかわかりません。

警告:ここで提供されるロックは、絶対的なものではなく、助言的なものです。競合する可能性のあるすべてのプロセスは、「AtomicOpen」クラスを使用する必要があります。


unlock_fileLinux上のファイルfcntlLOCK_UNフラグで再び呼び出すべきではありませんか?
eadmaster

ロック解除は、ファイルオブジェクトが閉じられると自動的に行われます。しかし、それを含めないのは私にとって悪いプログラミング習慣でした。コードを更新し、fcntlロック解除操作を追加しました!
Thomas Lux

では__exit__、あなたcloseの外側のロックの後にunlock_file。ランタイムは中にデータをフラッシュ(つまり、書き込み)できると思いますclose。私は、ロック中にロックの外に追加のデータが書き込まれないようにするために、ロックの下にいる必要があるflushと思います。fsyncclose
ベンジャミンバニエ

訂正ありがとうございます!およびなしで競合状態が発生する可能性があることを確認しました。を呼び出す前に、あなたが提案した2行を追加しました。再テストしたところ、競合状態は解消されたようです。flushfsyncunlock
Thomas Lux

1
「間違っている」唯一のことは、プロセス1がファイルをロックするまでに、その内容が切り捨てられる(内容が消去される)ことです。ロックの前に、上記のコードに「w」を含む別のファイル「open」を追加することで、これを自分でテストできます。ただし、ロックする前にファイルを開く必要があるため、これは避けられません。明確に言うと、「アトミック」とは、正当なファイルのコンテンツのみがファイル内で見つかるという意味です。これは、複数の競合するプロセスが混在するコンテンツを含むファイルを決して取得しないことを意味します。
Thomas Lux

15

私が好むロックファイルプラットフォームに依存しないファイルロック-


3
このライブラリはよく書かれているように見えますが、古いロックファイルを検出するメカニズムはありません。ロックを作成したPIDを追跡するため、そのプロセスがまだ実行中であるかどうかを判別できるはずです。
シェルバン

1
@sherbang:remove_existing_pidfileはどうですか?
Janus Troelsen 2013年

@JanusTroelsen pidlockfileモジュールはロックをアトミックに取得しません。
シェルバン2013年

@sherbangよろしいですか?ロックファイルをモードO_CREAT | O_EXCLで開きます。
mhsmith 2013年

2
このライブラリは取り替えられ
harlowja

13

私はそれを行うためのいくつかの解決策を見てきましたが、私の選択は oslo.concurrencyでした

それは強力であり、比較的よく文書化されています。それはファスナーに基づいています。

その他の解決策:

  • Portalocker:PIPを介して可能そうではない、exeファイルのインストールですpywin32を必要とし、
  • ファスナー:文書化が不十分
  • lockfile:非推奨
  • flufl.lock:POSIXシステム用のNFSセーフファイルロック。
  • simpleflock:最終更新2013-07
  • zc.lockfile:最終更新2016-06(2017-03現在)
  • lock_file:2007-10の最終更新

re:Portalocker、pypiwin32パッケージを介してpipからpywin32をインストールできるようになりました。
Timothy Jannace


13

ロックはプラットフォームとデバイスに固有ですが、通常、いくつかのオプションがあります。

  1. flock()または同等のものを使用します(OSがサポートしている場合)。これはアドバイザリロックであり、ロックを確認しない限り無視されます。
  2. lock-copy-move-unlock方法論を使用します。ファイルをコピーし、新しいデータを書き込んでから、移動します(移動ではなく移動-移動はLinuxではアトミック操作です-OSを確認してください)。ロックファイルの存在。
  3. ディレクトリを「ロック」として使用します。NFSはflock()をサポートしていないため、これはNFSに書き込む場合に必要です。
  4. プロセス間で共有メモリを使用する可能性もありますが、私はこれを試したことはありません。これは非常にOS固有です。

これらすべての方法では、ロックの取得とテストにスピンロック(失敗後の再試行)テクニックを使用する必要があります。これは、誤同期のための小さなウィンドウを残しますが、一般的には大きな問題にならないほど十分に小さいです。

クロスプラットフォームのソリューションを探している場合は、他のメカニズムを使用して別のシステムにログを記録することをお勧めします(次の最良の方法は、上記のNFSテクニックです)。

sqliteは通常のファイルと同じNFS上の制約を受けるため、ネットワーク共有上のsqliteデータベースに書き込み、無料で同期を取得することはできません。


4
注:移動/名前変更はWin32ではアトミックではありません。参考:stackoverflow.com/questions/167414/...
sherbang

4
新しい注意:os.renamePython 3.3以降、Win32でアトミックになりました:bugs.python.org/issue8828
Ghostkeeper

7

OSレベルでの単一ファイルへのアクセスの調整には、おそらく解決したくないあらゆる種類の問題が伴います。

あなたの最善の策は、そのファイルへの読み取り/書き込みアクセスを調整する別のプロセスを持っていることです。


19
「そのファイルへの読み取り/書き込みアクセスを調整する個別のプロセス」-言い換えれば、データベースサーバーを実装する:-)
Eli Bendersky 2009年

1
これが実際の最良の答えです。「データベースサーバーを使用する」と言うのは非常に単純化されています。これは、dbが常にジョブに適したツールになるとは限らないためです。プレーンテキストファイルにする必要がある場合はどうなりますか?良い解決策は、子プロセスを生成してから、名前付きパイプ、UNIXソケット、または共有メモリを介してアクセスすることです。
Brendon Crawford、

9
これは説明のない単なるFUDなので、-1です。書き込みのためにファイルをロックすることは、OSがそのような機能を提供するという私にはかなり単純な概念のように思えますflock。「独自のミューテックスとそれらを管理するデーモンプロセスをロールする」というアプローチは、解決するにはかなり極端で複雑なアプローチのように思われます。
Mark Amery

@Mark Ameryによって与えられた理由、およびOPが解決したい問題についての根拠のない意見を提供するため
マイケルクレブス

5

ファイルのロックは通常、プラットフォーム固有の操作であるため、さまざまなオペレーティングシステムで実行できるようにする必要がある場合があります。例えば:

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"

7
すでにご存知かもしれませんが、実行中のプラットフォームに関する情報を取得するために、プラットフォームモジュールも利用できます。platform.system()。docs.python.org/library/platform.html
monkut 2009年

2

私は、同じディレクトリ/フォルダ内から同じプログラムの複数のコピーを実行し、エラーをログに記録する、このような状況に取り組んできました。私のアプローチは、ログファイルを開く前に「ロックファイル」をディスクに書き込むことでした。プログラムは、続行する前に「ロックファイル」の存在を確認し、「ロックファイル」が存在する場合はその順番を待ちます。

これがコードです:

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ステートメントの直後にあったため、この編集では削除しました。


2

追加するにはエヴァンFossmarkの答えは、ここで使用する方法の例ですfilelockは

from filelock import FileLock

lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
    file = open(path, "w")
    file.write("123")
    file.close()

with lock:ブロック内のコードはスレッドセーフです。つまり、別のプロセスがファイルにアクセスする前にコードが終了します。


1

シナリオそのようなものです:ユーザーが何かをするファイルを要求します。次に、ユーザーが同じ要求を再度送信すると、最初の要求が完了するまで2番目の要求が行われないことをユーザーに通知します。そのため、この問題を処理するためにロックメカニズムを使用しています。

これが私の作業コードです:

from lockfile import LockFile
lock = LockFile(lock_file_path)
status = ""
if not lock.is_locked():
    lock.acquire()
    status = lock.path + ' is locked.'
    print status
else:
    status = lock.path + " is already locked."
    print status

return status

0

grizzled-pythonからのシンプルで機能する(!)実装を見つけました。

単純な使用os.open(...、O_EXCL)+ os.close()はWindowsでは機能しませんでした。


4
O_EXCLオプションはロックに関連していません
Sergei

0

あなたは見つけることが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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.