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

Jay Shaw 2023年6月21日
  1. Python での複数プロセスによるリソース共有の影響
  2. ファイルの状態と Python でのファイルのロックへの影響
  3. Python でファイルをロックする
  4. まとめ
Python でファイルをロックする

更衣室など、日常生活の中で 2 人が同時にアクセスできないリソースもあります。 すべての更衣室には、一度に 1 人だけがアクセスできるロックがあります。

2 人が同時にアクセスすると、混乱と困惑を引き起こす可能性があります。 そのため、共有リソースはロックで保護されています。

同様に、プログラミングでは、2つのプロセスまたはスレッドが同じリソースを共有するたびに、ロックを使用して回避しなければならない問題が発生する可能性があります。 この記事では、Python でファイルをロックする方法について説明します。

Python での複数プロセスによるリソース共有の影響

このセクションでは、Python でファイルをロックすることが重要である理由を明確にします。

このプログラムには 2つのプロセスがあります。最初に口座に 1 ドルを入金し、別の口座から 1 ドルを引き落とし、最後に残高が出力されます。 この操作はループ内で何千回も実行されます。

技術的には、同じ数が何度も足されたり差し引かれたりするため、反復によって最終的な残高が変化することはありません。 しかし、プログラムがコンパイルされると、結果は変わります。

import multiprocessing


# Withdrawal function


def wthdrw(bal):
    for _ in range(10000):
        bal.value = bal.value - 1


# Deposit function


def dpst(bal):
    for _ in range(10000):
        bal.value = bal.value + 1


def transact():
    # initial balance
    bal = multiprocessing.Value("i", 100)

    # creating processes
    proc1 = multiprocessing.Process(target=wthdrw, args=(bal,))
    proc2 = multiprocessing.Process(target=dpst, args=(bal,))

    # starting processes
    proc1.start()
    proc2.start()

    # waiting for processes to finish
    proc1.join()
    proc2.join()

    # printing final balance
    print("Final balance = {}".format(bal.value))


if __name__ == "__main__":

    for _ in range(10):
        # Performing transaction process
        transact()

出力:

C:\python38\python.exe "C:\Users\Win 10\main.py"
Final balance = 428
Final balance = 617
Final balance = -1327
Final balance = 1585
Final balance = -130
Final balance = -338
Final balance = -73
Final balance = -569
Final balance = 256
Final balance = -426

Process finished with exit code 0

なぜそれが起こるのかを理解するために、コード例を分解してみましょう。

このプログラムは、Python ライブラリ パッケージ multiprocessing を使用して、メソッドをプログラム内のプロセスとして呼び出します。

wthdrwdpst の 2つの方法は、アカウントにお金を差し引いて追加します。 それらには、バランスを表す 1つのパラメーター bal があります。

メソッド内では、for ループ内で bal の値がインクリメントまたはデクリメントされます。 反復は 10000 回繰り返されます。

bal.value は、このプログラムの共有リソースです。

# Method to Decrement
def wthdrw(bal):
    for _ in range(10000):
        bal.value = bal.value - 1

新しいメソッド transact() は、プロセスを順番に起動します。 メソッド内で初期値を格納するオブジェクトbalを作成し、残高を100に設定しています。

def transact():
    bal = multiprocessing.Value("i", 100)
    lock = multiprocessing.Lock()

multiprocessing ライブラリを使用すると、オブジェクトを使用するプロセスとしてメソッドを起動できます。 たとえば、メソッド wthdrw は、プロセス proc1 を作成することによって起動されます。

このプロセスは wthdrw メソッドを対象とし、bal と空のオブジェクトを引数として渡します。

proc1 = multiprocessing.Process(target=wthdrw, args=(bal,))

同様に、dpst メソッドを起動するために proc2 プロセスが作成されます。 両方のプロセスが作成されると、process_name.start() を使用して開始されます。

proc1.start()
proc2.start()

実行中のプロセスを閉じるには、process_name.join() 構文が使用されます。 実行中のプロセスの背後にリクエストをキューに入れるため、システムはプロセスが終了するのを待ってから閉じます。

処理が完了すると、最終的な残高が印刷されます。

print("Final balance = {}".format(bal.value))

__name__ 変数は __main に設定され、メソッド transact()for ループ内で呼び出されます。 これにより、プロセスを手動で呼び出すことなく、反復を複数回観察できます。

if __name__ == "__main__":

    for _ in range(10):
        transact()

プログラムを実行すると、値に一貫性がないことがわかりました。 共有リソース bal.value はロックで保護されていないため、実行中のプロセスが終了する前に他のプロセスが値を編集します。

この問題を解決するためにロックが使用されます。 上記のプログラムは、両方の方法でロックを設定することによってロックされます。 このように、コンパイラは前のプロセスが完了する前に次のプロセスの実行を待機する必要があります。

# Withdrawal function
def wthdrw(bal, lock):
    for _ in range(10000):
        # Locks
        lock.acquire()
        bal.value = bal.value - 1
        lock.release()

lock.acquire() ステートメントはプロセスをロックし、その中で bal の値が編集されます。 その後、ロックは lock.release() を使用して解放されます。

dpst メソッドにも同じことが適用され、残りのプログラムは同じままです。

出力:

C:\python38\python.exe "C:\Users\Win 10\main.py"
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100

Process finished with exit code 0

共有リソースを使用する複数のプロセスに何が起こるかがわかったので、Python でファイルをロックするために必要な重要な機能であるファイルの状態を見ていきます。

ファイルの状態と Python でのファイルのロックへの影響

ファイルの状態は、ファイルが開いているか閉じているかを示します。 ロック操作は開いているファイルに対してのみ実行できるため、Python でファイルをロックするには、ファイルの状態を知ることが重要です。

Python に、myFile.txtというファイルがあるとします。 書くとき:

readMyFile = open("myFile.txt", "r").read()

ファイルの状態を取得したり、閉じたり、その他のことを行うことはできません。 どこにも保存されていないのでアクセスはありません。

情報を破棄しても意味がなく、ガベージ コレクションが不可能なため、失われた知識を取り戻す魔法はありません。

変数 (リスト、dict メンバー、またはインスタンスの属性) で使用することを意図した値を保存しないでください。これにより、使用できるようになります。

myFile = open("myFile.txt", "r")
readMyFile = myFile.read()
print(myFile.closed)

myFile.close()
print(myFile.closed)

上記のコードは、ファイルを変数 myFile に保存します。 次に、別の変数 readMyFile は、myFile 変数を使用してオブジェクトを読み取ります。

この方法を使用すると、読み取りが完了した後もファイルが開いた状態のままになります。

構文 print(myFile.closed) は、現在のファイルの状態を示します。 ここでは、false 値を返します。これは、閉じられていないことを意味します。 このファイルは、myFile.close() を使用して明示的に閉じられます。

明示的にファイルを閉じると人的エラーが発生しやすいため、より良い解決策は with ステートメントを使用することです。

with open("myFile.txt", "r") as myFile:
    readMyFile = myFile.read()

現在、myFile は明示的に閉じる必要なく自動的に閉じられます。 上記の例から、Python でファイルをロックするには、ファイルを開閉する効率的な方法が不可欠であることがわかりました。

ファイルは開いた状態でのみロックでき、ロックが存在しない場合はファイルのロックを解除できません。 Python でのファイルのロックは、常に with ブロック内で実行する必要があります。

Python でファイルをロックするいくつかの方法を見てみましょう。

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

ファイル ロックは、通常、スクリプト内でファイルをロックするために使用されるサード パーティ製のライブラリです。 このセクションでは、広く使用されているファイル ロックについて説明します。

Python で FileLock を使用してファイルをロックする

これは、Windows と UNIX の両方で機能するプラットフォーム固有のファイル ロック ライブラリです。 インストールするには、次のコマンドを入力します。

pip install filelock

この方法でファイルをロックするのは非常に簡単です。 with ステートメント内で FileLock を使用してファイルを開くと、ファイルが開き、システムによってロックされます。

処理が完了すると、with ステートメントの最後で自動的にロックが解除されます。

from filelock import FileLock

with FileLock("myfile.txt"):
    print("Lock acquired.")

出力:

C:\python38\python.exe "C:/main.py"
Lock acquired.

Process finished with exit code 0

Python でファイルをロックする別の方法は、タイムアウトを使用することです。 これは、ファイルをロックできない場合に時間制限を設定するのに役立ち、他のプロセスのファイル ハンドルを解放します。

from filelock import FileLock

file = "example.txt"
lockfile = "example.txt.lock"

lock = FileLock(lockfile, timeout=5)

lock.acquire()
with open(file, "a") as f:
    print("File Locked")
    f.write("Add some data \n")
lock.release()


with open(file, "a") as f:
    f.write("Second time writing \n")

コードの最初の行は、filelock ライブラリから FileLock モジュールをインポートします。 example.txt ファイルは可変ファイル内に保存されます。 このファイルは、それに書き込むために使用されます。

ロックは元のファイルに直接配置されないため、example.txt.lock のような一時ファイルが作成されます。 変数 lock を使用して lockfile のロック オブジェクトを作成し、5 秒のタイムアウトを設定します。

このロックは lock.acquire ステートメントを使用して設定できます。 with ブロックは、ファイルを開いて書き込みを行うために使用されますが、lockfile は、書き込み中に元のファイルにアクセスする他のプロセスを回避します。

最後に、lock.release() を使用してロックを解除します。 次に、別のプロセスがファイルを開き、正常に書き込みます。

出力:

C:\python38\python.exe "C:/main.py"
File Locked

Process finished with exit code 0

FileLock を使用した Python ロック ファイル - 出力

Python を使用してファイルをロックするには、ロックをネストした方法で配置することもできます。

from filelock import Timeout, FileLock

lock = FileLock("high_ground.txt.lock")
with lock:
    with open("high_ground.txt", "a") as f:
        f.write("You were the chosen one.")

上記の例では、Python でファイルをロックするために、high_ground.txt という名前のファイルを使用して lock オブジェクトが作成されます。 ロックはブロック内に配置されます。 その内部では、ファイルは別のブロックを使用して読み取られます。

この方法は、前の方法よりもコードが少なくて済みます。

FileLock はプラットフォームに依存します。 すべてのアプリケーション インスタンスが同じプラットフォームで動作する場合は、FileLock を使用します。 それ以外の場合は、SoftFileLock を使用します。

SoftFileLock はプラットフォームに依存せず、ロック ファイルの存在のみを監視します。 このため、移植性が非常に高く、アプリケーションがクラッシュするとフリーズする可能性が高くなります。

このような場合は、ロックファイルを削除してください。

ファイル ロッカーは、例外処理ブロックでも使用できます。

try:
    with lock.acquire(timeout=10):
        with open(file_path, "a") as f:
            f.write("I have a bad feeling about this.")
except Timeout:
    print("Another instance of this application currently holds the lock.")

このコード スニペットは、try ブロック内にロックを配置し、10 秒間のタイムアウトを与えます。 次に、ネストされた with 内で、ファイルが書き込まれます。

except ブロック内では、アプリケーションがタイムアウトした場合に適切なメッセージが出力されるように設定されています。

Python で PortaLocker を使用してファイルをロックする

Python でファイルをロックするもう 1つのオプションは、ファイル ロック用のシンプルな API を提供する portallocker というライブラリを使用することです。

Linux および Unix システムでは、デフォルトでロックが推奨されることを覚えておくことが重要です。 Linux では、mount コマンドに-o mandオプションを追加することで、必須のファイル ロックを有効にすることができます。

インストールするには、次のコマンドを入力します。

pip install "portalocker[redis]"

Python で portallocker を使用してファイルをロックする方法は、FileLock メソッドに似ていますが、はるかに簡単です。

import portalocker

file = "example.txt"
with portalocker.Lock(file, "a") as fh:
    fh.write("first instance")
    print("waiting for your input")
    input()

出力:

C:\python38\python.exe "C:/main.py"

lock acquired
waiting for your input

上記のコードでは、最初の行でライブラリ パッケージをインポートしています。 ロックは構文 portallocker.RedisLock() を使用して作成され、ロックは with ステートメントを使用して配置されます。

ロックをタイムアウト内に配置するには、次を使用します。

import portalocker

file = "example.txt"
with portalocker.Lock("file", timeout=1) as fh:
    print("writing some stuff to my cache...", file=fh)

出力:

PortaLocker を使用した Python ロック ファイル - 出力

まとめ

この記事では、Python でファイルをロックする方法と、それが重要な理由について説明します。 この記事を読んだ後、読者はオープン状態のファイルを作成し、それらに簡単にロックをかけることができます。

関連記事 - Python File