检查Python脚本是否正在运行。

129

我有一个作为我的 Web 应用程序一部分运行的 Python 守护进程。如何快速检查是否正在运行,如果没有,则启动它?

我想这样做是为了解决守护进程的任何崩溃,并且脚本不必手动运行,只要调用即可自动运行并保持运行。

我该如何使用 Python 检查我的脚本是否正在运行?


你确定你想用Python编写让一个进程保持另一个进程运行的程序吗? - ojblass
试试 Tendo,它可以创建你的脚本的单例实例,因此如果已经在运行,则脚本将不会再次运行。https://github.com/pycontribs/tendo - JasTonAChair
这不是你的守护进程的工作,而是由“上层”应用程序来启动你的守护进程的工作。使用systemd或类似supervisord的其他工具。不要依赖于写入文件的pid。如果无法使用systemd/supervisord,则使用锁定确保它不会被执行两次。 - guettli
21个回答

177

在Linux系统上很方便的一种技术是使用域套接字:

import socket
import sys
import time

def get_lock(process_name):
    # Without holding a reference to our socket somewhere it gets garbage
    # collected when the function exits
    get_lock._lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

    try:
        # The null byte (\0) means the socket is created 
        # in the abstract namespace instead of being created 
        # on the file system itself.
        # Works only in Linux
        get_lock._lock_socket.bind('\0' + process_name)
        print 'I got the lock'
    except socket.error:
        print 'lock exists'
        sys.exit()


get_lock('running_test')
while True:
    time.sleep(3)

使用原子操作可以避免如果进程被发送 SIGKILL 信号时,锁文件散落在各处的问题。

您可以在 socket.close的文档 中了解到,当垃圾回收时,套接字会自动关闭。


22
未来的谷歌用户请注意:此代码使用“抽象套接字”,这是特定于Linux的(并非一般的posix)。有关更多信息,请参见:http://blog.eduardofleury.com/archives/2007/09/13 - georg
4
好的。但我想知道为什么要将lock_socket定义为全局变量。我进行了测试,如果lock_socket不被定义为全局变量,则在运行多个进程时,锁定系统将无法正常工作。为什么?lock_socket只在get_lock函数中定义和使用。为什么它必须被定义为全局变量? - Alptugay
8
我写这篇文章已经有一段时间了,我的记忆有点模糊。但我认为原因是,否则它会被垃圾回收并关闭套接字。大概就是这样。 - aychedee
8
空字节(\0)表示套接字在抽象命名空间中创建,而不是在文件系统本身上创建。 - aychedee
2
你可以这样做...但是如果你想要更改脚本的名称怎么办?或者更重要的是,如果同时启动了两个副本脚本怎么办?我相信如果我正确地管理时间,我可以启动它们中的两个而不让它们意识到另一个也已经启动了。这个锁定文件机制是原子的。这意味着它不能被两个不同的进程占用。 - aychedee
显示剩余13条评论

109

在某个地方放置一个pid文件(例如/tmp)。然后,您可以通过检查文件中的PID是否存在来检查进程是否正在运行。在干净关闭时不要忘记删除该文件,并在启动时检查它。

#/usr/bin/env python

import os
import sys

pid = str(os.getpid())
pidfile = "/tmp/mydaemon.pid"

if os.path.isfile(pidfile):
    print "%s already exists, exiting" % pidfile
    sys.exit()
file(pidfile, 'w').write(pid)
try:
    # Do some actual work here
finally:
    os.unlink(pidfile)

你可以检查/tmp/mydaemon.pid文件中的内容是否为一个存在的进程,以确定该进程是否正在运行。可以使用上面提到的Monit来完成此操作,也可以编写一个简单的shell脚本并使用ps命令的返回代码来检查它。

ps up `cat /tmp/mydaemon.pid ` >/dev/null && echo "Running" || echo "Not running"

如果您想额外加分,可以使用atexit模块确保您的程序在任何情况下(比如被杀死、引发异常等)都能清理其pid文件。


8
如果程序已经崩溃,os.unlink() 就不会执行并且程序不会重新运行,因为文件已经存在。对吗? - Yuda Prawira
2
正确的,但这可能是预期的行为。如果pidfile存在但其中的PID未运行,则表示非正常关闭,这意味着应用程序崩溃了。这让你知道出现了问题,并且需要检查日志。如上所述,假设漏洞不在Python解释器本身中,atexit模块也可以处理此问题。 - Dan Udey
8
虽然这是一个简单的解决方案,但它容易受到竞态条件的影响。如果脚本的两个实例在大约相同的时间内被执行,那么if os.path.isfile(pidfile)可能会对两者都评估为false,导致它们都写入锁定文件并继续运行。 - Cerin
8
操作系统也会重复使用PID,因此可能会出现假阳性。 - aychedee
12
注意,对于使用Python 3的用户来说,函数file()已经被移除了,需要使用open()。即使您使用的是Python 2.7版本,也应该使用open()而非file(),详情请见:https://docs.python.org/2/library/functions.html#file (是的,如果您使用的是早期的Python 2.2版本,则官方建议与现在相反。显然,他们改变了主意。) - jpk
显示剩余2条评论

27

pid库可以准确地完成这个任务。

from pid import PidFile

with PidFile():
  do_something()
它还将自动处理pid文件存在但进程未运行的情况。

这个工作得非常好。只需要以root身份运行才能在Ubuntu上运行。+1 - Jimmy
14
@Jimmy,你可以使用PidFile(piddir='/home/user/run/')等方式来指定一个你有权限放置pid文件的目录。这样你就不需要以root身份运行了。 - Decko
我认为按照这里所描述的使用临时目录作为piddir的选项是一个不错的选择。 - Rishi Latchmepersad
@RishiLatchmepersad 使用gettempdir不是一个好主意,因为每次调用都会给出一个唯一的目录,这将破坏pid检查。该目录需要在每次脚本运行时保持相同。 - Decko
在某些情况下,您可能需要手动强制删除pid文件:pidfile.close(fh=pidfile.fh, cleanup=True) - Airstriker

11

我的解决方案是检查进程和命令行参数 在Windows和Ubuntu Linux上测试过

import psutil
import os

def is_running(script):
    for q in psutil.process_iter():
        if q.name().startswith('python'):
            if len(q.cmdline())>1 and script in q.cmdline()[1] and q.pid !=os.getpid():
                print("'{}' Process is already running".format(script))
                return True

    return False


if not is_running("test.py"):
    n = input("What is Your Name? ")
    print ("Hello " + n)

除了 @nst 的回答,这是更好的答案。 - shgnInc
你需要确保脚本是通过 python .. 启动的,而不是直接调用 ./<script name>,否则它将无法工作,因为它检查进程是否以 python 开始。 - DMin

11

当然,来自Dan的示例不会按照应有的方式运行。

事实上,如果脚本崩溃、引发异常或无法清理pid文件,则脚本将运行多次。

我建议基于另一个网站的以下内容:

这是为了检查是否已经存在锁定文件。

\#/usr/bin/env python
import os
import sys
if os.access(os.path.expanduser("~/.lockfile.vestibular.lock"), os.F_OK):
        #if the lockfile is already there then check the PID number
        #in the lock file
        pidfile = open(os.path.expanduser("~/.lockfile.vestibular.lock"), "r")
        pidfile.seek(0)
        old_pid = pidfile.readline()
        # Now we check the PID from lock file matches to the current
        # process PID
        if os.path.exists("/proc/%s" % old_pid):
                print "You already have an instance of the program running"
                print "It is running as process %s," % old_pid
                sys.exit(1)
        else:
                print "File is there but the program is not running"
                print "Removing lock file for the: %s as it can be there because of the program last time it was run" % old_pid
                os.remove(os.path.expanduser("~/.lockfile.vestibular.lock"))

这是放置PID文件在锁文件中的代码部分

pidfile = open(os.path.expanduser("~/.lockfile.vestibular.lock"), "w")
pidfile.write("%s" % os.getpid())
pidfile.close()

这段代码将检查pid的值是否与已有运行中的进程相比,以避免重复执行。

希望这能有所帮助。


3
应该使用os.kill(old_pid, 0),它在各种UNIX系统中更加通用。如果PID不存在或属于不同的用户,它会引发OSError异常。 - drdaeman
2
请注意,使用/proc/<pid>来检查进程非常不可移植,并且只能在Linux上可靠地工作。 - Dan Udey

9

在寻找解决方法时,我偶然发现了这个老问题。

使用psutil

import psutil
import sys
from subprocess import Popen

for process in psutil.process_iter():
    if process.cmdline() == ['python', 'your_script.py']:
        sys.exit('Process found: exiting.')

print('Process not found: starting it.')
Popen(['python', 'your_script.py'])

此脚本必须以sudo身份运行,否则将会出现访问被拒绝的错误。 - DoesData
1
如果您从命令行传递参数到脚本中,那么列表也将包含所有这些参数。 - DoesData

9

在UNIX系统中,有非常好的包可以用来重新启动进程。其中一个拥有出色教程的软件是monit。通过一些调整,你可以使用这个经过验证的可靠技术来保持你的守护进程稳定运行。


我同意,不要重复造轮子,有很多方法可以将你的应用程序守护化,包括在应用程序死亡时重新启动它,如果没有运行则启动等等。 - davr

7

有很多选择。一种方法是使用系统调用或Python库来替您执行此类调用。另一种方法则是简单地生成一个进程,例如:

ps ax | grep processName

并解析输出。许多人选择这种方法,在我看来并不一定是一个坏方法。


processName会包括我的脚本文件名吗? - Josh Hunt
这取决于你如何启动你的进程。 - ojblass
ps ax | grep python - User

2
一个依赖于 multiprocessing.shared_memory 的可移植解决方案:
import atexit
from multiprocessing import shared_memory

_ensure_single_process_store = {}


def ensure_single_process(name: str):
    if name in _ensure_single_process_store:
        return
    try:
        shm = shared_memory.SharedMemory(name='ensure_single_process__' + name,
                                         create=True,
                                         size=1)
    except FileExistsError:
        print(f"{name} is already running!")
        raise
    _ensure_single_process_store[name] = shm
    atexit.register(shm.unlink)

通常情况下,您不需要使用atexit,但有时在异常退出时清理会很有帮助。

2

试试这个其他版本

def checkPidRunning(pid):        
    '''Check For the existence of a unix pid.
    '''
    try:
        os.kill(pid, 0)
    except OSError:
        return False
    else:
        return True

# Entry point
if __name__ == '__main__':
    pid = str(os.getpid())
    pidfile = os.path.join("/", "tmp", __program__+".pid")

    if os.path.isfile(pidfile) and checkPidRunning(int(file(pidfile,'r').readlines()[0])):
            print "%s already exists, exiting" % pidfile
            sys.exit()
    else:
        file(pidfile, 'w').write(pid)

    # Do some actual work here
    main()

    os.unlink(pidfile)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接