【问题标题】:How to delete a locked (flock) file without race condition: before or after releasing the lock?如何在没有竞争条件的情况下删除锁定(flock)文件:在释放锁之前或之后?
【发布时间】:2016-01-14 23:15:54
【问题描述】:

我正在实现基于文件锁的互斥机制。我的脚本的其他实例知道它们在遇到特定文件时不应该运行,该文件被锁定。

为了实现这一点,我使用fcntl.flock 创建并锁定了文件。当我释放锁时,我还想清理文件,当没有进程实际运行时,它不会坐在那里指示旧的 pid。

我的问题是,我应该何时以及如何清理文件,尤其是在什么时候可以删除它。基本上我看到两个选项:

  • 在释放锁之前截断并删除文件
  • 锁释放后截断并删除文件

据我了解,每一个都将我的应用程序暴露在稍微不同的竞争条件下。什么是最佳做法,我错过了什么?

这是一个(过于简化的)示例:

import fcntl
import os
import sys
import time

# open file for read/write, create if necessary
with open('my_lock_file.pid', 'a+') as f:
    # acquire lock, raises exception if lock is hold by another process
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        print 'other process running:', f.readline()
        sys.exit()

    try:
        # do something
        f.write('%d\n' % os.getpid())
        f.flush()
        # more stuff here ...
        time.sleep(5)
    finally:
        # clean up here?
        # release lock
        fcntl.flock(f, fcntl.LOCK_UN)
        # clean up here?
# clean up here?

【问题讨论】:

标签: python ipc delete-file flock


【解决方案1】:

我找到了这个相关的问题,就如何处理这种情况给出一些建议:

这也让我意识到另一个可能的竞争条件,当另一个进程在当前进程打开文件后删除文件时会发生这种情况。 这将导致当前进程锁定文件系统上不再存在的文件,从而无法阻止下一个将重新创建它的进程。

在那里我发现了使用打开标志O_EXCL 进行原子独占文件创建的建议,该标志通过os.open() 函数公开用于低级文件操作。 然后我相应地实现了以下示例:

import os
import sys
import time

# acquire: open file for write, create if possible, exclusive access guaranteed
fname = 'my_lock_file.pid'
try:
    fd = os.open(fname, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
except OSError:
    # failed to open, another process is running
    with open(fname) as f:
        print "other process running:", f.readline()
        sys.exit()

try:
    os.write(fd, '%d\n' % os.getpid())
    os.fsync(fd)
    # do something
    time.sleep(5)
finally:
    os.close(fd)
    # release: delete file
    os.remove(fname)

在实现之后,我发现这与lockfile 模块用于其pid 文件的方法完全相同。

供参考:

【讨论】:

  • 如果脚本在“做某事”步骤中异常退出,锁定文件不会被删除。以后运行脚本的所有尝试都将打印“其他进程正在运行”消息,因为如果文件已经存在,os.O_EXCL 会导致发生错误。因此,fasteners 包中提供的进程锁等其他方法不会清理锁文件。如果您想允许删除锁定文件,我认为您需要在获取锁定后按照您链接到的答案中的建议对文件进行统计。
  • @ws_e_c421 事实上,我注意到lockfile 模块同时被弃用了。 fasteners 只需使用 OP 中给出的方法(基于 fcntl.flock()),不要费心清理(截断和删除)文件。
  • 时隔两年再次阅读,我花了一段时间才理解我的评论。通过异常退出,我的意思是在 Python C 扩展中遇到 seg 错误,因此 finally 块永远不会删除锁定文件。这是我个人遇到的情况。另外,我关于清理的一点是fasteners 不会尝试使用删除锁定文件,只需使用fcntl.flock() 释放锁定并关闭文件描述符。
【解决方案2】:
  1. 在 unix 中,可以在打开文件时删除文件 - ino 将一直保留到所有进程结束且文件描述符列表中包含该文件
  2. 在 unix 中,可以通过检查链接计数变为零来检查文件是否已从所有目录中删除

当每次都创建锁定文件时,这可能是 unix 下的有效解决方案:

import fcntl
import os
import sys
import time

# open file for read/write, create if necessary
for attempt in xrange(timeout):
    with open('my_lock_file.pid', 'a+') as f:
        # acquire lock, raises exception if lock is hold by another process
        try:
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
            st = os.fstat(f)
            if not st.st_nlink:
                print 'lock file got deleted'
                continue # try again
        except BlockingIOError:
            print 'other process running:', f.readline()
            time.sleep(1)
            continue # try again

        try:
            # do something
            f.write('%d\n' % os.getpid())
            f.flush()
            # more stuff here ...
            time.sleep(5)
        finally:
            # clean up here
            os.unlink('my_lock_file.pid')
            # release lock
            fcntl.flock(f, fcntl.LOCK_UN)
    return True
return False

作业:解释为什么删除文件的传统名称在 unix 中被命名为“unlink”。

【讨论】:

  • 这将在文件被删除时起作用,但在文件被重命名时不起作用。为了解决这个问题,flock 成功 stat 文件 by_name 后,将 inode 编号与从flocked 文件句柄上的现有 stat 获得的 inode 编号进行比较。如果数字相同 - 成功。如果没有,请再试一次。
  • 我认为 time.sleep(1) 是不必要的,因为下一次flock会等待
猜你喜欢
  • 2013-07-16
  • 2013-12-23
  • 2013-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-09
  • 1970-01-01
  • 2012-01-10
相关资源
最近更新 更多