在Python中锁定一个文件

在我们的日常生活中,有些资源是不能由两个人同时进入的,比如说,更衣室。每个更衣室都有一把锁,每次只有一个人可以进入。

如果两个人同时访问,可能会引起混乱和尴尬。所以,这就是为什么共享资源要用锁来保护。

同样,在编程中,只要两个进程或线程共享同一资源,就会产生问题,必须通过使用锁来避免。本文将解释如何在Python中锁定一个文件。

Python中多个进程共享资源的影响

本节将阐明为什么在Python中锁定一个文件很重要。

这个程序有两个进程:第一个在一个账户中存入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:python38python.exe "C:UsersWin 10main.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 ,在账户中扣钱和加钱。它们有一个参数,bal ,代表余额。

在该方法中,bal 的值在for 循环中被递增或递减。迭代重复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,))

同样地,创建一个proc2 进程来启动dpst 方法。一旦这两个进程被创建,它们将使用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:python38python.exe "C:UsersWin 10main.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:python38python.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:python38python.exe "C:/main.py"
File Locked
Process finished with exit code 0

在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.")

在上面的例子中,创建了一个lock 对象和一个名为high_ground.txt 的文件,在Python中锁定一个文件。该锁被放在一个块内;在该块内,使用另一个块读取文件。

这种方法需要的代码比前一种少。

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中锁定文件的另一个选择是使用称为portalocker 的库,它为文件锁定提供了一个简单的API。

关键是要记住,在Linux和Unix系统上,锁是默认的。在Linux上可以通过在mount命令中添加-o mand 选项来启用强制性的文件锁定。

要安装它,请输入该命令:

pip install "portalocker[redis]"

在Python中使用portalocker 锁定文件与FileLock 方法类似,但要简单得多。

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

输出:

C:python38python.exe "C:/main.py"
lock acquired
waiting for your input

在上面的代码中,第一行导入了库包。使用语法portalocker.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中锁定一个文件

结语

这篇文章解释了如何在Python中锁定一个文件以及为什么它很重要。读者在读完这篇文章后,可以轻松地创建开放状态的文件并给它们加锁。