Python:用于创建基于PID的锁文件的模块?

28 投票
7 回答
24246 浏览
提问于 2025-04-15 14:25

我正在写一个Python脚本,这个脚本可能会运行很长时间,也可能不会,这取决于很多因素。我想确保多个实例(通过定时任务cron启动的)不会互相干扰。看起来最合理的办法是使用基于PID的锁文件……不过,如果已经有现成的代码可以做到这一点,我就不想再自己重新发明轮子了。

所以,有没有哪个Python模块可以帮我管理基于PID的锁文件的细节呢?

7 个回答

7

我对这些都不太满意,所以我写了这个:

class Pidfile():
    def __init__(self, path, log=sys.stdout.write, warn=sys.stderr.write):
        self.pidfile = path
        self.log = log
        self.warn = warn

    def __enter__(self):
        try:
            self.pidfd = os.open(self.pidfile, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
            self.log('locked pidfile %s' % self.pidfile)
        except OSError as e:
            if e.errno == errno.EEXIST:
                pid = self._check()
                if pid:
                    self.pidfd = None
                    raise ProcessRunningException('process already running in %s as pid %s' % (self.pidfile, pid));
                else:
                    os.remove(self.pidfile)
                    self.warn('removed staled lockfile %s' % (self.pidfile))
                    self.pidfd = os.open(self.pidfile, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
            else:
                raise

        os.write(self.pidfd, str(os.getpid()))
        os.close(self.pidfd)
        return self

    def __exit__(self, t, e, tb):
        # return false to raise, true to pass
        if t is None:
            # normal condition, no exception
            self._remove()
            return True
        elif t is PidfileProcessRunningException:
            # do not remove the other process lockfile
            return False
        else:
            # other exception
            if self.pidfd:
                # this was our lockfile, removing
                self._remove()
            return False

    def _remove(self):
        self.log('removed pidfile %s' % self.pidfile)
        os.remove(self.pidfile)

    def _check(self):
        """check if a process is still running

the process id is expected to be in pidfile, which should exist.

if it is still running, returns the pid, if not, return False."""
        with open(self.pidfile, 'r') as f:
            try:
                pidstr = f.read()
                pid = int(pidstr)
            except ValueError:
                # not an integer
                self.log("not an integer: %s" % pidstr)
                return False
            try:
                os.kill(pid, 0)
            except OSError:
                self.log("can't deliver signal to %s" % pid)
                return False
            else:
                return pid

class ProcessRunningException(BaseException):
    pass

可以像这样使用:

try:
    with Pidfile(args.pidfile):
        process(args)
except ProcessRunningException:
    print "the pid file is in use, oops."
13

这可能对你有帮助:lockfile

11

如果你可以使用GPLv2许可证,Mercurial有一个相关的模块:

http://bitbucket.org/mirror/mercurial/src/tip/mercurial/lock.py

下面是一个使用的例子:

from mercurial import error, lock

try:
    l = lock.lock("/path/to/lock", timeout=600) # wait at most 10 minutes
    # do something
except error.LockHeld:
     # couldn't take the lock
else:
    l.release()

撰写回答