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

#1 楼

好的,所以我最终使用了我在此处编写的代码,在我的网站链接上已死,在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


评论


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

–leetNightshade
2012年11月8日在21:27



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

– Stuart Berg
2014年2月20日在16:21



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

– jweyrich
2014-12-19 13:40



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

–哈本
16年4月28日在9:10



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

–哈利
18年6月6日at 13:53

#2 楼

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

尽管凯文说过,但要尽可能避免一次从多个进程写入文件。

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

评论


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

– caryary
09年1月29日在5:38

Portalocker需要使用Windows的Python扩展。

–n611x007
13年2月21日在9:59

@naxa有一个变体,它仅依赖于msvcrt和ctypes,请参见roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/…

–Shmil The Cat
13年4月15日在21:21

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

–沃尔夫
2016年9月6日下午0:03

SQLite支持并发访问吗?

–piotr
19年11月5日在22:37

#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中使用
警告:

如果在Windows上运行并且Python在调用exit之前崩溃,则无法确定锁定行为。
警告: />此处提供的锁定仅供参考,并非绝对。所有可能竞争的进程都必须使用“ AtomicOpen”类。
(从2020年11月9日开始),此代码仅锁定Posix系统上的可写文件。在发布之后和此日期之前的某个时候,在只读文件上使用open是违法的。


评论


linux上的unlock_file文件是否不应该使用LOCK_UN标志再次调用fcntl?

–管理员
18-11-16在15:26



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

– Thomas Lux
18/12/3在15:36

在__exit__中,您在unlock_file之后在锁之外关闭。我相信运行时可以在关闭期间刷新(即写入)数据。我相信必须在锁下进行刷新和fsync,以确保在关闭期间不会在锁外写入其他数据。

–本杰明·班尼尔(Benjamin Bannier)
19年1月7日在8:44

感谢您的指正!我验证了没有刷新和fsync的竞争状况的可能性。在调用解锁之前,我已经添加了您建议的两行。我重新测试,比赛条件似乎已解决。

– Thomas Lux
19年1月8日23:51

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

– Thomas Lux
19年1月15日在19:27

#4 楼

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

评论


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

–sherbang
2011年12月28日在19:06

@sherbang:那么remove_existing_pidfile呢?

– Janus Troelsen
2013年3月15日16:06

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

–sherbang
13年3月15日在20:25

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

–mhsmith
2013年6月21日14:53

请注意,该库已被取代,它是github.com/harlowja/fasteners的一部分

– congusbongus
18-10-29在22:48

#5 楼

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

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

其他解决方案:



Portalocker:需要pywin32,这是exe安装,因此无法通过pip

紧固件:文献记载不充分

锁文件:不推荐使用

flufl.lock:POSIX系统的NFS安全文件锁定。 simpleflock:最新更新2013-07

zc.lockfile:最新更新2016-06(截至2017-03)

lock_file:最新更新为2007-10


评论


回复:Portalocker,您现在可以通过pypiwin32软件包通过pip安装pywin32。

–提莫西·詹纳斯(Timothy Jannace)
18-09-18在18:23

也有文件锁(最新发布:在评论时为2019年5月18日)

– jfs
19年8月5日在6:52

#6 楼

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


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

对于所有这些方法,您都必须使用自旋锁(失败后重试)技术来获取和测试锁。这确实为错误同步留下了一个小窗口,但是通常它足够小,不会成为主要问题。通过某种其他机制登录到另一个系统(下一个最好的事情是上面的NFS技术)。

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

评论


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

–sherbang
2011-12-27 21:28

新说明:从Python 3.3开始,os.rename现在在Win32中是原子的:bugs.python.org/issue8828

–Ghostkeeper
16年8月29日在1:27

#7 楼

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

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

评论


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

– Eli Bendersky
09年1月31日在8:39

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

–布伦登·克劳福德(Brendon Crawford)
2011年7月22日4:55



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

–马克·阿默里(Mark Amery)
16年5月10日在11:38



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

– Michael Krebs
19年5月2日在13:38

#8 楼

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

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"


评论


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

– monkut
09年1月29日在0:54

#9 楼

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

这是代码:

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


编辑---
在考虑了有关过时锁定的一些注释之后,我编辑了代码以添加“ lock file”过时检查。从我的系统开始,对该函数进行数千次迭代,平均获得0.002066 ...秒的时间,从之前开始:

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


到之后: />
remove('errloglock')


所以我想我将以5倍的数量开始,以表明陈旧并监视问题的发生。

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

lock.close()


,我紧随open语句,因此在此编辑中已将其删除。

#10 楼

为了补充Evan Fossmark的答案,下面是一个如何使用文件锁的示例:

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

#11 楼

情况是这样的:
用户请求文件做某事。然后,如果用户再次发送相同的请求,它将通知用户第二个请求直到第一个请求完成后才完成。因此,我使用锁定机制来解决此问题。

这是我的工作代码:

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


#12 楼

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

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

评论


O_EXCL选项与锁定无关

–谢尔盖
14年4月16日在10:31

#13 楼

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