web-dev-qa-db-ja.com

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

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

127
Evan Fosmark

さて、私が書いたコードを使用することになりました ここで、私のウェブサイト上リンクは無効です、archive.orgで表示 ( GitHubでも利用可能 )。次の方法で使用できます。

from filelock import FileLock

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

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

Kevinが言うように、一度に複数のプロセスからファイルに書き込むことは、可能な限り避けたいものです。

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

38
John Fouhy

他のソリューションは、多くの外部コードベースを引用しています。自分でやりたい場合は、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は、通常withステートメントを使用するopenブロックで使用できます。

警告:exitが呼び出される前にWindows上でPythonがクラッシュする場合、私にはわかりませんロック動作がどうなるか。

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

16
Thomas Lux

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

14
ferrdo

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

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

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

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

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

12

私はそれを行うためのいくつかのソリューションを見てきましたが、私の選択は oslo.concurrency です

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

その他の解決策:

10
Maxime Viargues

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

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

7
Kevin

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

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"
5
Greg Hewgill

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

コードは次のとおりです。

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
whitebeard

scenarioは次のようなものです。ユーザーはファイルに何かを要求します。次に、ユーザーが同じ要求を再度送信すると、最初の要求が完了するまで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
1

私はgrizzled-pythonからシンプルで動作する(!) 実装 を見つけました。

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

0
Speq

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
0
Cobry