在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
,在程序中作为进程调用方法。
两个方法,wthdrw
和dpst
,在账户中扣钱和加钱。它们有一个参数,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锁定一个文件,我们也可以用嵌套的方式来放置锁:
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中锁定一个文件以及为什么它很重要。读者在读完这篇文章后,可以轻松地创建开放状态的文件并给它们加锁。