如何在Python中正确实现基于进程的锁?

时间:2014-07-17 07:08:55

标签: python linux

问题陈述:

我有一个从外部工具调用的脚本,并在一组文件中维护其实例的状态。我认为处理它的最实用的方法是简单地使用单个锁来序列化脚本实例,具有被获取的能力(a)当它尚未被锁定时,(b)在它被释放之后(c) )在保持它的过程消失后。

我还不确定是否有必要在现有进程崩溃或某事时立即唤醒下一个等待进程,因为这无论如何都是一种特殊情况。但当然,下一个动作(可能由重启触发)必须能够成功运行。

该脚本依赖于NetworkManager,而NetworkManager目前只在Linux上运行。因此,简单的解决方案优于跨平台解决方案。另一方面,跨平台解决方案可能对大量stackoverflow访问者有用。

进一步讨论:

我在stackoverflow上找到了一些相关的问题和答案,但(1)问题并不像这个问题那样具体,(2)答案似乎不适用于这种情况。特别是关于处理陈旧锁的部分大多没有得到解决。

我想继续使用上下文管理器API,只使用Linux安装中常见的库。我想在标准库和任何常见安装中都没有完美的解决方案,所以我认为我需要使用一些较低级别的API 实现上下文管理器。

当前代码使用的是 lockfile 模块,它似乎根本不关心过时的锁。除了文件系统之外,脚本实例不会共享任何内容,因此基于多处理模块的解决方案似乎不适用于此处。我正在考虑 pidfile fcntl 的组合,还有一个可用于等待其他脚本完成的 unix socket 。我想知道为什么我在Python中找不到基于标准上下文管理器的工具。

相关脚本的实时版本(将在接受新修补程序时更改):

http://www.nlnetlabs.nl/svn/dnssec-trigger/trunk/dnssec-trigger-script.in

源代码的相关部分:

def run(self):
    with lockfile.FileLock("/var/run/dnssec-trigger/dnssec-trigger"):
        log.debug("Running: {}".format(self.method.__name__))
        self.method()

2 个答案:

答案 0 :(得分:2)

您可以使用contextlib创建自己的上下文管理器,并使用fcntl发出锁定调用。请注意,这些可以是非阻塞的。

contextlibfcntl都是标准库的一部分。

对于您尝试过时锁定,您可以尝试两次启动该过程并向其中一个发出SIGKILL - 您应该看到在另一个进程上释放锁定。

import fcntl
import contextlib

@contextlib.contextmanager
def lock(fname):
    with open(fname, "w") as f:
        print "Acquiring lock"
        fcntl.lockf(f, fcntl.LOCK_EX)
        print "Acquired lock"

        yield

        print "Releasing lock"
        fcntl.lockf(f, fcntl.LOCK_UN)
        print "Released lock"


if __name__ == "__main__":
    import os
    print "PID:", os.getpid()

    import time
    print "Starting"
    with lock("/tmp/lock-file"):
        time.sleep(100)
    print "Done"

答案 1 :(得分:1)

我在上游提交了以下实现:

class Lock:
    """Lock used to serialize the script"""

    path = "/var/run/dnssec-trigger/lock"

    def __init__(self):
        # We don't use os.makedirs(..., exist_ok=True) to ensure Python 2 compatibility
        dirname = os.path.dirname(self.path)
        if not os.path.exists(dirname):
            os.makedirs(dirname)
        self.lock = open(self.path, "w")

    def __enter__(self):
        fcntl.lockf(self.lock, fcntl.LOCK_EX)

    def __exit__(self, t, v, tb):
        fcntl.lockf(self.lock, fcntl.LOCK_UN)

它不是完全通用的(路径名称是硬编码的),它不会关闭文件描述符,并且可能会以其他方式进行改进,但我仍然希望将其包含在内以供参考。