在Python中打开文件进行独占访问的最佳方法是什么?

时间:2008-10-09 06:58:55

标签: python file locking

解决这个问题最优雅的方法是什么:

  • 打开一个文件进行阅读,但前提是尚未打开文件
  • 打开一个文件进行写作,但前提是它尚未打开以供阅读或写作

内置函数的工作原理如下

>>> 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)。

解决方案应该在同一个过程中(如上例所示)以及另一个进程打开文件时工作。
如果崩溃程序不能保持锁定打开,则首选。

7 个答案:

答案 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而不是根本不共享)。另请参阅CreateFileWriteFile系统调用的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.rename
try:
    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'模式打开它,即使在阅读模式下也不会再打开它,就像您想要的那样......