【发布时间】:2010-10-04 03:24:59
【问题描述】:
我需要锁定一个文件以便用 Python 编写。它将同时从多个 Python 进程访问。我在网上找到了一些解决方案,但大多数都因我的目的而失败,因为它们通常仅基于 Unix 或基于 Windows。
【问题讨论】:
标签: python file-locking
我需要锁定一个文件以便用 Python 编写。它将同时从多个 Python 进程访问。我在网上找到了一些解决方案,但大多数都因我的目的而失败,因为它们通常仅基于 Unix 或基于 Windows。
【问题讨论】:
标签: python file-locking
好的,所以我最终使用了我编写的代码 here, on my websitelink is dead, view on archive.org (also available on GitHub)。我可以通过以下方式使用它:
from filelock import FileLock
with FileLock("myfile.txt"):
# work with the file as it is now locked
print("Lock acquired.")
【讨论】:
这里有一个跨平台的文件锁定模块:Portalocker
尽管正如 Kevin 所说,一次从多个进程写入文件是您希望尽可能避免的事情。
如果您可以将问题硬塞到数据库中,则可以使用 SQLite。它支持并发访问并处理自己的锁定。
【讨论】:
其他解决方案引用了很多外部代码库。如果您更愿意自己动手,这里有一些跨平台解决方案的代码,该解决方案在 Linux / DOS 系统上使用了相应的文件锁定工具。
try:
# Posix based file locking (Linux, Ubuntu, MacOS, etc.)
# Only allows locking on writable files, might cause
# strange results for reading.
import fcntl, os
def lock_file(f):
if f.writable(): fcntl.lockf(f, fcntl.LOCK_EX)
def unlock_file(f):
if f.writable(): 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 语句。
警告:
fcntl.lock 是非法的。【讨论】:
unlock_file linux 上的文件不应该使用LOCK_UN 标志再次调用fcntl?
__exit__ 你close 在unlock_file 之后的锁之外。我相信运行时可以在close 期间刷新(即写入)数据。我相信必须在锁下flush 和fsync 以确保在close 期间没有额外的数据写入锁外。
flush 和fsync 的情况下存在竞争条件的可能性。在致电unlock 之前,我已经添加了您建议的两行代码。我重新测试,竞争条件似乎得到了解决。
我一直在寻找几种解决方案来做到这一点,我的选择是 oslo.concurrency
它功能强大且文档相对完善。它基于紧固件。
其他解决方案:
【讨论】:
filelock package 与接受的答案相同,其中:pip3 install filelock
filelock 你链接的星号和叉子比接受的答案包更多
我更喜欢 lockfile — 独立于平台的文件锁定
【讨论】:
锁定是特定于平台和设备的,但通常,您有几个选项:
对于所有这些方法,您必须使用自旋锁(失败后重试)技术来获取和测试锁。这确实为错误同步留下了一个小窗口,但它通常足够小,不会成为主要问题。
如果您正在寻找跨平台的解决方案,那么您最好通过其他机制登录到另一个系统(其次是上面的 NFS 技术)。
请注意,sqlite 受到与普通文件相同的 NFS 约束,因此您无法写入网络共享上的 sqlite 数据库并免费获得同步。
【讨论】:
os.rename 现在在 Win32 中是原子的,因为 Python 3.3:bugs.python.org/issue8828
在操作系统级别协调对单个文件的访问充满了您可能不想解决的各种问题。
最好有一个单独的进程来协调对该文件的读/写访问。
【讨论】:
flock 这样的功能。 “滚动你自己的互斥体和一个守护进程来管理它们”的方法似乎是一种相当极端和复杂的方法来解决......一个你实际上没有告诉我们的问题,但只是可怕地建议存在。跨度>
这里是一个如何使用filelock库的例子,它类似于Evan Fossmark's implementation:
from filelock import FileLock
lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
file = open(path, "w")
file.write("123")
file.close()
with lock: 块中的任何代码都是线程安全的,这意味着它将在另一个进程访问该文件之前完成。
【讨论】:
filelock 模块,就像 Evan 的模块公开了一个 FileLock 类一样,与 Evan 的工作完全无关。你可以在 GitHub 上看到,Evan 在github.com/dmfrey/FileLock/blob/master/filelock/filelock.py 的代码与github.com/tox-dev/py-filelock/tree/main/src/filelock 的代码没有共享代码或祖先,这就是你在这里使用的。
锁定文件通常是特定于平台的操作,因此您可能需要考虑在不同操作系统上运行的可能性。例如:
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"
【讨论】:
我一直在处理这样的情况,我从同一目录/文件夹中运行同一程序的多个副本并记录错误。我的方法是在打开日志文件之前将“锁定文件”写入磁盘。程序在继续之前检查“锁定文件”是否存在,如果“锁定文件”存在则等待轮到它。
代码如下:
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')
编辑--- 在考虑了上面关于过时锁的一些 cmets 之后,我编辑了代码以添加对“锁文件”过时的检查。在我的系统上对该函数的数千次迭代计时,平均为 0.002066... 秒:
lock = open('errloglock', 'w')
紧接着:
remove('errloglock')
所以我想我会从 5 倍的数量开始,以指示陈旧性并监控问题的情况。
此外,在处理时间安排时,我意识到我有一些并非真正需要的代码:
lock.close()
我在打开声明之后立即删除了它,所以我在这次编辑中删除了它。
【讨论】:
if not exists('errloglock') 和lock = open('errloglock', 'w') 之间访问。
这对我有用: 不占用大文件,分几个小文件 您创建文件 Temp,删除文件 A,然后将文件 Temp 重命名为 A。
import os
import json
def Server():
i = 0
while i == 0:
try:
with open(File_Temp, "w") as file:
json.dump(DATA, file, indent=2)
if os.path.exists(File_A):
os.remove(File_A)
os.rename(File_Temp, File_A)
i = 1
except OSError as e:
print ("file locked: " ,str(e))
time.sleep(1)
def Clients():
i = 0
while i == 0:
try:
if os.path.exists(File_A):
with open(File_A,"r") as file:
DATA_Temp = file.read()
DATA = json.loads(DATA_Temp)
i = 1
except OSError as e:
print (str(e))
time.sleep(1)
【讨论】:
场景是这样的: 用户请求一个文件来做某事。然后,如果用户再次发送相同的请求,它会通知用户第二个请求没有完成,直到第一个请求完成。这就是为什么,我使用锁机制来处理这个问题。
这是我的工作代码:
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
【讨论】:
我从 grizzled-python 中找到了一个简单且有效的(!)implementation。
简单的使用 os.open(..., O_EXCL) + os.close() 在 windows 上不起作用。
【讨论】:
您可能会发现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
【讨论】: