在Python中锁定文件


152

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

Answers:


115

好吧,所以我最后讲了我在这里编写的代码,在我的网站上的链接已失效,在archive.org查看也可以在GitHub上查看)。我可以按以下方式使用它:

from filelock import FileLock

with FileLock("myfile.txt.lock"):
    print("Lock acquired.")
    with open("myfile.txt"):
        # work with the file as it is now locked

10
正如博客文章中的评论所指出的那样,该解决方案不是“完美的”,因为程序可能会终止,从而导致锁留在原处,并且您必须在文件之前手动删除该锁。再次变得可访问。但是,除此之外,这仍然是一个很好的解决方案。
leetNightshade 2012年

3
Evan FileLock的另一个改进版本可以在这里找到:github.com/ilastik/lazyflow/blob/master/lazyflow/utility/…–
Stuart Berg

3
OpenStack确实发布了自己的(很好,跳过Montanaro的)实现-pylockfile-与前面的评论中提到的实现非常相似,但仍然值得一看。
jweyrich 2014年

7
@jweyrich Openstacks pylockfile现在已弃用。建议使用紧固件oslo.concurrency代替。
harbun

2
我猜是另一个类似的实现:github.com/benediktschmitt/py-filelock
herry

39

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

尽管正如Kevin所说,一次要避免从多个进程写入文件是要避免的。

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


16
+1-在这种情况下,SQLite几乎总是行之有效的方法。
09年

2
Portalocker需要使用Windows的Python扩展。
n611x007

2
@naxa有它的一个变体,它仅仅依赖MSVCRT和ctypes的,请参阅roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/...
Shmil猫

@ n611x007 Portalocker刚刚更新,因此在Windows上不再需要任何扩展:)
Wolph

2
SQLite支持并发访问吗?
piotr

23

其他解决方案引用了大量外部代码库。如果您愿意自己做,这里是一些跨平台解决方案的代码,该解决方案使用Linux / DOS系统上的相应文件锁定工具。

try:
    # Posix based file locking (Linux, Ubuntu, MacOS, etc.)
    import fcntl, os
    def lock_file(f):
        fcntl.lockf(f, fcntl.LOCK_EX)
    def unlock_file(f):
        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语句的块中使用。

警告:如果在Windows上运行并且Python在调用exit之前崩溃,我不确定锁定行为将是什么。

警告:此处提供的锁定仅供参考,并非绝对的。所有可能竞争的进程都必须使用“ AtomicOpen”类。


unlock_filelinux上的文件是否不应该fcntl再次使用LOCK_UN标志调用?
eadmaster '18

文件对象关闭时,解锁会自动发生。但是,不包含它是我不好的编程习惯。我已经更新了代码并添加了fcntl解锁操作!
Thomas Lux

__exit__close外锁之后unlock_file。我相信运行时可以在期间刷新(即写入)数据close。我相信,一个必须flushfsync下锁,以确保没有额外的数据在锁定外写入close
本杰明·班尼尔

感谢您的纠正!我证实没有对于没有竞争条件的可能性flushfsync。在致电之前,我已经添加了您建议的两行unlock。我重新测试,比赛条件似乎已解决。
Thomas Lux

1
唯一会出错的是,到进程1锁定文件时,其内容将被截断(内容将被删除)。您可以通过在锁定之前的上述代码中添加另一个带有“ w”的文件“ open”来自己进行测试。但是这是不可避免的,因为您必须在锁定文件之前将其打开。为了明确起见,“原子”是指在文件中只能找到合法文件内容的意义。这意味着您将永远不会获得包含来自多个相互竞争的进程的内容的文件。
Thomas Lux

15

我更喜欢lockfile —与平台无关的文件锁定


3
这个库写得不错,但是没有检测过期锁文件的机制。它跟踪创建锁的PID,因此应该可以判断该进程是否仍在运行。
sherbang 2011年

1
@sherbang:那么remove_existing_pidfile呢?
Janus Troelsen

@JanusTroelsen pidlockfile模块不会自动获取锁。
sherbang 2013年

@sherbang你确定吗?它将以O_CREAT | O_EXCL模式打开锁定文件。
mhsmith 2013年

2
请注意,该库已被取代,它是github.com/harlowja/fasteners的
congusbongus

13

我一直在寻找几种解决方案,而我的选择是 oslo.concurrency

它功能强大且有据可查。它基于紧固件。

其他解决方案:


回复:Portalocker,您现在可以通过pypiwin32软件包通过pip安装pywin32。
蒂莫西·詹娜斯


13

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

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

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

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

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


4
注意:在Win32中,“移动/重命名”不是原子的。参考:stackoverflow.com/questions/167414/...
sherbang

4
新注:os.rename自Python 3.3以来,它在Win32中是原子的:bugs.python.org/issue8828
Ghostkeeper,2016年

7

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

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


19
“协调对该文件的读/写访问的单独过程”-换句话说,实现数据库服务器:-)
Eli Bendersky,2009年

1
这实际上是最好的答案。仅仅说“使用数据库服务器”过于简单了,因为数据库并不总是适合做这项工作。如果需要纯文本文件怎么办?一个好的解决方案可能是生成一个子进程,然后通过命名管道,Unix套接字或共享内存访问它。
布伦登·克劳福德

9
-1,因为这只是FUD,没有说明。在我看来,锁定文件以进行写入似乎是一个非常简单的概念,操作系统提供了类似flock的功能。“滚动自己的互斥锁和守护进程来管理它们”的方法似乎是解决问题的一种极端且复杂的方法……您实际上并没有告诉我们有关问题,只是存在一些建议。
马克·阿默里

-1是由@Mark Amery给出的原因,以及针对OP要解决的问题提供了未经证实的意见
Michael Krebs

5

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

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"

7
您可能已经知道这一点,但是平台模块也可用于获取正在运行的平台上的信息。platform.system()。docs.python.org/library/platform.html
monkut

2

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

这是代码:

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

编辑---考虑了以上有关过时锁定的一些评论之后,我编辑了代码,以添加“锁定文件”过时检查。在我的系统上,对该函数定时进行数千次迭代,平均之前为0.002066 ...秒:

lock = open('errloglock', 'w')

到之后:

remove('errloglock')

因此,我认为我将以5倍的数量开始,以表明陈旧性并监视问题情况。

另外,当我在计时的时候,我意识到我确实有一些不必要的代码:

lock.close()

我已经紧跟在open语句之后,因此在此编辑中已将其删除。


2

为了补充Evan Fossmark的答案,这是一个如何使用filelock的示例

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


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

0

我从grizzled-python 找到了一个简单且可行的实现

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


4
O_EXCL选项与锁定无关
Sergei 2014年

0

您可能会发现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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.