解决这个问题最优雅的方法是什么:
内置函数的工作原理如下
>>> path = r"c:\scr.txt"
>>> file1 = open(path, "w")
>>> print file1
<open file 'c:\scr.txt', mode 'w' at 0x019F88D8>
>>> file2 = open(path, "w")
>>> print file2
<open file 'c:\scr.txt', mode 'w' at 0x02332188>
>>> file1.write("111")
>>> file2.write("222")
>>> file1.close()
scr.txt现在包含'111'。
>>> file2.close()
scr.txt被覆盖,现在包含'222'(在Windows上,Python 2.4)。
解决方案应该在同一个过程中(如上例所示)以及另一个进程打开文件时工作。
如果崩溃程序不能保持锁定打开,则首选。
答案 0 :(得分:23)
我认为没有完全跨平台的方式。在unix上,fcntl模块将为您执行此操作。但是在Windows上(我假设你是路径),你需要使用win32file模块。
幸运的是,有一个可移植的实现(portalocker)在python cookbook上使用平台适当的方法。
要使用它,请打开该文件,然后调用:
portalocker.lock(file, flags)
其中flags是portalocker.LOCK_EX,用于独占写访问,或LOCK_SH用于共享,读访问。
答案 1 :(得分:9)
解决方案应该在同一个进程中工作(如上例所示)以及另一个进程打开文件时。
如果通过'另一个进程'表示'无论进程'(即不是您的程序),在Linux中,只有系统调用( fcntl &amp; friends)才能实现此目的。你想要的是mandatory locking,并且获得它的Linux方式更复杂:
使用 mand 选项重新安装包含文件的分区:
# mount -o remount,mand /dev/hdXY
为您的文件设置 sgid 标志:
# chmod g-x,g+s yourfile
在Python代码中,获取该文件的独占锁:
fcntl.flock(fd, fcntl.LOCK_EX)
现在,即使 cat 在释放锁定之前也无法读取文件。
答案 2 :(得分:3)
这是便携式实现的win32一半的开始,不需要单独的锁定机制。
需要Python for Windows Extensions才能使用win32 api,但这对于Windows上的python来说几乎是必须的,也可以使用ctypes来完成。如果需要,可以调整代码以暴露更多功能(例如允许FILE_SHARE_READ
而不是根本不共享)。另请参阅CreateFile
和WriteFile
系统调用的MSDN文档以及article on Creating and Opening Files。
如前所述,如果需要,您可以使用标准fcntl模块实现unix的一半。
import winerror, pywintypes, win32file
class LockError(StandardError):
pass
class WriteLockedFile(object):
"""
Using win32 api to achieve something similar to file(path, 'wb')
Could be adapted to handle other modes as well.
"""
def __init__(self, path):
try:
self._handle = win32file.CreateFile(
path,
win32file.GENERIC_WRITE,
0,
None,
win32file.OPEN_ALWAYS,
win32file.FILE_ATTRIBUTE_NORMAL,
None)
except pywintypes.error, e:
if e[0] == winerror.ERROR_SHARING_VIOLATION:
raise LockError(e[2])
raise
def close(self):
self._handle.close()
def write(self, str):
win32file.WriteFile(self._handle, str)
以下是您的示例如何表现:
>>> path = "C:\\scr.txt"
>>> file1 = WriteLockedFile(path)
>>> file2 = WriteLockedFile(path) #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
LockError: ...
>>> file1.write("111")
>>> file1.close()
>>> print file(path).read()
111
答案 3 :(得分:3)
假设你的Python解释器,以及底层的os和文件系统将os.rename视为原子操作,并且当目标存在时它会出错,以下方法没有竞争条件。我正在linux机器上使用它。不需要第三方库,也不依赖于操作系统,除了额外的文件创建外,许多用例都可以接受性能提升。您可以在这里轻松应用python的函数装饰器模式或'with_statement'上下文管理器来抽象出混乱。
在新流程/任务开始之前,您需要确保lock_filename不存在。
import os,time
def get_tmp_file():
filename='tmp_%s_%s'%(os.getpid(),time.time())
open(filename).close()
return filename
def do_exclusive_work():
print 'exclusive work being done...'
num_tries=10
wait_time=10
lock_filename='filename.lock'
acquired=False
for try_num in xrange(num_tries):
tmp_filename=get_tmp_file()
if not os.path.exists(lock_filename):
try:
os.rename(tmp_filename,lock_filename)
acquired=True
except (OSError,ValueError,IOError), e:
pass
if acquired:
try:
do_exclusive_work()
finally:
os.remove(lock_filename)
break
os.remove(tmp_filename)
time.sleep(wait_time)
assert acquired, 'maximum tries reached, failed to acquire lock file'
修改强>
已经发现,os.rename会在非Windows操作系统上静默覆盖目标。谢谢你指出这个@ akrueger!
以下是从here收集的解决方法:
您可以使用:
而不是使用os.renametry:
if os.name != 'nt': # non-windows needs a create-exclusive operation
fd = os.open(lock_filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL)
os.close(fd)
# non-windows os.rename will overwrite lock_filename silently.
# We leave this call in here just so the tmp file is deleted but it could be refactored so the tmp file is never even generated for a non-windows OS
os.rename(tmp_filename,lock_filename)
acquired=True
except (OSError,ValueError,IOError), e:
if os.name != 'nt' and not 'File exists' in str(e): raise
@ akrueger你可能对你的基于目录的解决方案很好,只是给你一个替代方法。
答案 4 :(得分:2)
编辑:我自己解决了这个问题!使用 目录存在 &amp;年龄作为锁定机制!按文件锁定仅在Windows上是安全的(因为Linux会无声地覆盖),但是按目录锁定在Linux和Windows上都能正常工作。请参阅我的GIT,我为此创建了一个易于使用的类'lockbydir.DLock':
https://github.com/drandreaskrueger/lockbydir
在自述文件的底部,您可以找到3个GIT播放器,您可以在其中看到代码示例在浏览器中实时执行!相当酷,不是吗? : - )
感谢您的关注
我想回答parity3(https://meta.stackoverflow.com/users/1454536/parity3),但我既不能直接评论('你必须有50个评论的声誉'),我也没有看到任何方式直接与他/她联系。你有什么建议,要通过他?
我的问题:
我已经实现了类似于这里建议的parity3作为答案的东西:https://stackoverflow.com/a/21444311/3693375(“假设你的Python解释器,以及......”)
它在Windows上运行得非常出色。 (我正在使用它来实现一个在独立启动的进程中工作的锁定机制。https://github.com/drandreaskrueger/lockbyfile)
但除了parity3之外,它在Linux上的工作方式不同:
os.rename(src,dst)
将文件或目录src重命名为dst。 ...在Unix上,如果dst存在 并且是一个文件, 如果用户有权限,它将被静默替换。 如果src和dst,某些Unix风格的操作可能会失败 在不同的文件系统上。如果成功,重命名将 是原子操作(这是POSIX要求)。 在Windows上,如果dst已存在,则会引发OSError (https://docs.python.org/2/library/os.html#os.rename)
无声替换是问题所在。在Linux上。 “如果dst已经存在,将引发OSError”对我来说非常好。但遗憾的是,仅在Windows上。
我想,奇偶校验3的例子大部分时间仍然有效,因为他的if条件
if not os.path.exists(lock_filename):
try:
os.rename(tmp_filename,lock_filename)
然而整个事情不再是原子的了。
因为在两个并行进程中if条件可能为真,然后两者都将重命名,但只有一个将赢得重命名竞赛。并且没有异常(在Linux中)。
有什么建议吗?谢谢!
P.S。:我知道这不是正确的方法,但我缺乏另类选择。请不要因为降低我的声誉而惩罚我。我环顾四周,自己解决这个问题。如何PM用户在这里? meh 为什么我不能?
答案 5 :(得分:2)
我更喜欢使用filelock,这是一个跨平台的Python库,几乎不需要任何其他代码。这是一个使用方法的示例:
from filelock import FileLock
lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
file = open(path, "w")
file.write("111")
file.close()
with lock:
块中的任何代码都是线程安全的,这意味着它将在另一个进程可以访问该文件之前完成。
答案 6 :(得分:0)
为了让您在一个应用程序中打开文件时安全,您可以尝试这样的事情:
import time
class ExclusiveFile(file):
openFiles = {}
fileLocks = []
class FileNotExclusiveException(Exception):
pass
def __init__(self, *args):
sMode = 'r'
sFileName = args[0]
try:
sMode = args[1]
except:
pass
while sFileName in ExclusiveFile.fileLocks:
time.sleep(1)
ExclusiveFile.fileLocks.append(sFileName)
if not sFileName in ExclusiveFile.openFiles.keys() or (ExclusiveFile.openFiles[sFileName] == 'r' and sMode == 'r'):
ExclusiveFile.openFiles[sFileName] = sMode
try:
file.__init__(self, sFileName, sMode)
finally:
ExclusiveFile.fileLocks.remove(sFileName)
else:
ExclusiveFile.fileLocks.remove(sFileName)
raise self.FileNotExclusiveException(sFileName)
def close(self):
del ExclusiveFile.openFiles[self.name]
file.close(self)
这样就可以继承file
类。现在就做:
>>> f = ExclusiveFile('/tmp/a.txt', 'r')
>>> f
<open file '/tmp/a.txt', mode 'r' at 0xb7d7cc8c>
>>> f1 = ExclusiveFile('/tmp/a.txt', 'r')
>>> f1
<open file '/tmp/a.txt', mode 'r' at 0xb7d7c814>
>>> f2 = ExclusiveFile('/tmp/a.txt', 'w') # can't open it for writing now
exclfile.FileNotExclusiveException: /tmp/a.txt
如果您先使用'w'模式打开它,即使在阅读模式下也不会再打开它,就像您想要的那样......