【问题标题】:Locking a file in Python在 Python 中锁定文件
【发布时间】:2010-10-04 03:24:59
【问题描述】:

我需要锁定一个文件以便用 Python 编写。它将同时从多个 Python 进程访问。我在网上找到了一些解决方案,但大多数都因我的目的而失败,因为它们通常仅基于 Unix 或基于 Windows。

【问题讨论】:

    标签: python file-locking


    【解决方案1】:

    好的,所以我最终使用了我编写的代码 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.")
    

    【讨论】:

    • 正如博客文章中的评论所指出的那样,此解决方案并不“完美”,因为程序可能会以这样一种方式终止,即锁留在原地并且您拥有在文件再次可访问之前手动删除锁。但是,除此之外,这仍然是一个很好的解决方案。
    • Evan 的 FileLock 的另一个改进版本可以在这里找到:github.com/ilastik/lazyflow/blob/master/lazyflow/utility/…
    • OpenStack 确实发布了他们自己的(好吧,Skip Montanaro 的)实现 - pylockfile - 与之前的 cmets 中提到的非常相似,但仍然值得一看。
    • @jweyrich Openstacks pylockfile 现在已弃用。建议改用fastenersoslo.concurrency
    • 我猜另一个类似的实现:github.com/benediktschmitt/py-filelock
    【解决方案2】:

    这里有一个跨平台的文件锁定模块:Portalocker

    尽管正如 Kevin 所说,一次从多个进程写入文件是您希望尽可能避免的事情。

    如果您可以将问题硬塞到数据库中,则可以使用 SQLite。它支持并发访问并处理自己的锁定。

    【讨论】:

    • +1 -- 在这种情况下,SQLite 几乎总是可行的方法。
    • Portallocker 需要适用于 Windows 的 Python 扩展。
    • @naxa 有一个变种,它只依赖于 msvcrt 和 ctypes,请参阅 roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/…
    • @n611x007 Portalocker 刚刚更新,因此它不再需要在 Windows 上进行任何扩展 :)
    • SQLite 支持并发访问吗?
    【解决方案3】:

    其他解决方案引用了很多外部代码库。如果您更愿意自己动手​​,这里有一些跨平台解决方案的代码,该解决方案在 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 语句。

    警告:

    • 如果在调用 exit 之前在 Windows 和 Python 上运行崩溃,我不确定锁定行为会是什么。
    • 此处提供的锁定是建议性的,不是绝对的。所有潜在的竞争进程都必须使用“AtomicOpen”类。
    • 截至 2020 年 11 月 9 日,此代码仅锁定 Posix 系统上的可写文件。在发布之后和此日期之前的某个时间点,在只读文件上使用 fcntl.lock 是非法的。

    【讨论】:

    • unlock_file linux 上的文件不应该使用LOCK_UN 标志再次调用fcntl
    • 文件对象关闭时会自动解锁。但是,不包括它是我不好的编程习惯。我已经更新了代码并添加了 fcntl 解锁操作!
    • __exit__closeunlock_file 之后的锁之外。我相信运行时可以在close 期间刷新(即写入)数据。我相信必须在锁下flushfsync 以确保在close 期间没有额外的数据写入锁外。
    • 感谢指正!我验证了在没有flushfsync 的情况下存在竞争条件的可能性。在致电unlock 之前,我已经添加了您建议的两行代码。我重新测试,竞争条件似乎得到了解决。
    • 唯一会“出错”的是,当进程 1 锁定文件时,它的内容将被截断(内容被删除)。您可以通过在 before 锁定上方的代码中添加另一个带有“w”的文件“open”来自行测试。但这是不可避免的,因为您必须在锁定文件之前打开文件。澄清一下,“原子”是指在文件中只能找到合法的文件内容。这意味着您永远不会得到一个包含来自多个竞争进程的内容混合在一起的文件。
    【解决方案4】:

    我一直在寻找几种解决方案来做到这一点,我的选择是 oslo.concurrency

    它功能强大且文档相对完善。它基于紧固件。

    其他解决方案:

    • Portalocker: 需要 pywin32,这是一个 exe 安装,所以不能通过 pip 安装
    • fasteners: 文档不完整
    • lockfile:已弃用
    • flufl.lock:用于 POSIX 系统的 NFS 安全文件锁定。
    • simpleflock : 最后更新 2013-07
    • zc.lockfile:上次更新时间为 2016 年 6 月(截至 2017 年 3 月)
    • lock_file : 最后更新于 2007-10

    【讨论】:

    • re: Portalocker,现在可以通过pypiwin32包通过pip安装pywin32了。
    • @jfs is not filelock package 与接受的答案相同,其中:pip3 install filelock
    • @alper:相同的模块名称,不同的包(看github)
    • @jfs 推荐使用哪一个? filelock 你链接的星号和叉子比接受的答案包更多
    【解决方案5】:

    我更喜欢 lockfile — 独立于平台的文件锁定

    【讨论】:

    • 这个库看起来写得很好,但是没有检测过时锁文件的机制。它跟踪创建锁的 PID,因此应该可以判断该进程是否仍在运行。
    • @sherbang:remove_existing_pidfile呢?
    • @JanusTroelsen pidlockfile 模块不会自动获取锁。
    • @sherbang 你确定吗?它以 O_CREAT|O_EXCL 模式打开锁定文件。
    • 请注意这个库已经被取代,是github.com/harlowja/fasteners的一部分
    【解决方案6】:

    锁定是特定于平台和设备的,但通常,您有几个选项:

    1. 使用flock() 或等效方法(如果您的操作系统支持)。这是建议锁定,除非您检查锁定,否则它会被忽略。
    2. 使用 lock-copy-move-unlock 方法,复制文件,写入新数据,然后移动它(移动,而不是复制 - 移动是 Linux 中的原子操作 - 检查您的操作系统),然后您检查锁定文件是否存在。
    3. 将目录用作“锁”。如果您要写入 NFS,这是必要的,因为 NFS 不支持flock()。
    4. 还有可能在进程之间使用共享内存,但我从未尝试过;它非常特定于操作系统。

    对于所有这些方法,您必须使用自旋锁(失败后重试)技术来获取和测试锁。这确实为错误同步留下了一个小窗口,但它通常足够小,不会成为主要问题。

    如果您正在寻找跨平台的解决方案,那么您最好通过其他机制登录到另一个系统(其次是上面的 NFS 技术)。

    请注意,sqlite 受到与普通文件相同的 NFS 约束,因此您无法写入网络共享上的 sqlite 数据库并免费获得同步。

    【讨论】:

    【解决方案7】:

    在操作系统级别协调对单个文件的访问充满了您可能不想解决的各种问题。

    最好有一个单独的进程来协调对该文件的读/写访问。

    【讨论】:

    • “协调对该文件的读/写访问的单独进程” - 换句话说,实现一个数据库服务器:-)
    • 这其实是最好的答案。仅仅说“使用数据库服务器”过于简单,因为数据库并不总是适合这项工作的工具。如果它需要是纯文本文件怎么办?一个好的解决方案可能是生成一个子进程,然后通过命名管道、unix 套接字或共享内存访问它。
    • -1 因为这只是 FUD 没有解释。锁定文件以进行写入对我来说似乎是一个非常简单的概念,操作系统为其提供了像flock 这样的功能。 “滚动你自己的互斥体和一个守护进程来管理它们”的方法似乎是一种相当极端和复杂的方法来解决......一个你实际上没有告诉我们的问题,但只是可怕地建议存在。跨度>
    • -1 出于@Mark Amery 给出的原因,以及就 OP 想要解决的问题提供未经证实的意见
    【解决方案8】:

    这里是一个如何使用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: 块中的任何代码都是线程安全的,这意味着它将在另一个进程访问该文件之前完成。

    【讨论】:

    • 这并没有增加埃文的答案,因为它与它完全正交,尽管您自己可能没有意识到这一点!有点令人困惑的是,您在答案中链接到的 PyPI 上的 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 的代码没有共享代码或祖先,这就是你在这里使用的。
    • 哇哦,我都没注意到。感谢您指出这一点,我相应地更新了我的帖子:)
    【解决方案9】:

    锁定文件通常是特定于平台的操作,因此您可能需要考虑在不同操作系统上运行的可能性。例如:

    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"
    

    【讨论】:

    【解决方案10】:

    我一直在处理这样的情况,我从同一目录/文件夹中运行同一程序的多个副本并记录错误。我的方法是在打开日志文件之前将“锁定文件”写入磁盘。程序在继续之前检查“锁定文件”是否存在,如果“锁定文件”存在则等待轮到它。

    代码如下:

    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') 之间访问。
    • @RokJaklič 如果有人编写程序来访问同一个日志文件而不检查锁定文件,那么你是对的。您会注意到我将应用程序限定为“同一程序的多个副本”。
    【解决方案11】:

    这对我有用: 不占用大文件,分几个小文件 您创建文件 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)
    

    【讨论】:

      【解决方案12】:

      场景是这样的: 用户请求一个文件来做某事。然后,如果用户再次发送相同的请求,它会通知用户第二个请求没有完成,直到第一个请求完成。这就是为什么,我使用锁机制来处理这个问题。

      这是我的工作代码:

      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
      

      【讨论】:

        【解决方案13】:

        我从 grizzled-python 中找到了一个简单且有效的(!)implementation

        简单的使用 os.open(..., O_EXCL) + os.close() 在 windows 上不起作用。

        【讨论】:

        • O_EXCL 选项与锁无关
        【解决方案14】:

        您可能会发现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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-18
          • 2014-12-10
          • 2014-04-25
          • 1970-01-01
          相关资源
          最近更新 更多