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
を使用して、メソッドをプログラム内のプロセスとして呼び出します。
wthdrw
と dpst
の 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
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)
出力:
まとめ
この記事では、Python でファイルをロックする方法と、それが重要な理由について説明します。 この記事を読んだ後、読者はオープン状態のファイルを作成し、それらに簡単にロックをかけることができます。