如何在Python中检查是否存在具有给定pid的进程?

140

有没有一种方法可以检查pid是否对应于有效的进程? 我从不同于os.getpid()的来源获取pid,并且需要检查该pid所对应的进程不存在于机器上。

我需要在Unix和Windows系统中使用。我还要检查PID是否未被使用。


5
Windows是一个非标准的操作系统。这种类型的东西不具备可移植性。你知道你无法兼得,那么你的优先选择是什么?选择一个作为优先事项,并编辑问题。 - S.Lott
38
“Windows是一个非标准操作系统。”这是我在SO上看到过最荒谬的评论之一。 - Piotr Dobrogost
3
@Piotr Dobrogost:您能提供处理 POSIX 标准 Unix 和非 POSIX 标准的 Windows 的代码吗?如果可以,请提供一个既解决了问题又清楚地表明 Windows 在某种程度上符合 POSIX 标准的答案。 - S.Lott
6
@PiotrDobrogost 我认为S.Lott的评论更多关于实现细节和API支持,而不是市场份额。 - Roy Tinker
6
相对于其他流行操作系统,Windows显然与它们之间的相似之处较少(任何进行Web开发的人可能会将其类比为同样臭名昭著的微软产品)。但是回应@S.Lott的话:我很少为Windows编写Python代码,而这些代码不打算在Linux、OSX、BSD等其他平台上运行,因此我真的认为“重点关注Windows”不是有益的建议,特别是因为Python尽可能地抽象了平台差异。 - Michael Scheper
仅限于Linux系统:import subprocess; subprocess.check_call('ps -p 12345', shell=True);,而跨平台使用则需要 psutil - Trevor Boyd Smith
15个回答

190

向进程ID发送信号0,如果该进程未在运行,则会引发OSError异常,否则不执行任何操作。

import os

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

11
在 Windows 上这是无效的,因为在 Windows 上不存在类似 UNIX 的信号。 - Alexander Lebedev
14
为了完整起见,您还应检查错误号以确保它为3(捕获异常并检查第一个参数)。如果目标进程存在但您无权发送信号(出于任何原因),这一点非常重要。 - haridsv
8
现在已经受到Windows的支持。http://docs.python.org/library/os.html?highlight=os.kill#os.kill - michael
19
os.kill在Windows上被支持,但是os.kill(pid, 0)os.kill(pid, signal.CTRL_C_EVENT)是相同的,这可能会终止进程(或失败)。当尝试对子进程执行此操作时,会出现OSError,其中errno==EINVAL - Jason R. Coombs
3
这段代码不正确,因为它没有考虑到 EPERM 错误,并且对于实际存在的 PID 返回 False。这是正确的答案:https://dev59.com/enRB5IYBdhLWcg3wn4fb#6940314。 - Giampaolo Rodolà
显示剩余8条评论

108

请看psutil模块:

psutil(python系统和进程工具)是一个跨平台的库,可以在Python中检索有关运行进程系统利用率(CPU、内存、磁盘、网络)的信息[...]。它目前支持LinuxWindowsOSXFreeBSDSun Solaris,包括32位64位架构,使用Python版本为2.6到3.4(Python 2.4和2.5的用户可以使用2.1.3版本),也已知可以在PyPy上运行。

它有一个名为pid_exists()的函数,你可以使用它来检查给定 pid 的进程是否存在。

这里是一个例子:

import psutil
pid = 12345
if psutil.pid_exists(pid):
    print("a process with pid %d exists" % pid)
else:
    print("a process with pid %d does not exist" % pid)

参考资料:


7
这里是关于psutil在POSIX系统中实现pid_exists()的方式。请看这个链接:https://github.com/giampaolo/psutil/blob/5ba055a8e514698058589d3b615d408767a6e330/psutil/_psposix.py#L28-L53。与`psutil`作者Giampaolo Rodolà在StackOverflow上的回答(https://dev59.com/enRB5IYBdhLWcg3wn4fb#6940314)进行比较。 - jfs
我曾经遇到过 pid_exists 在 Windows 上不再可靠的情况。它会错误地报告已死亡的 pid 仍在使用中。这提醒我们需要定期更新 psutil 模块,特别是在进行操作系统升级时。 - Damon Brodie

70

mluebke的代码并非完全正确;kill()函数也可能会引发EPERM(访问被拒绝)错误,这意味着进程确实存在。以下代码应该可以正常工作:

(根据Jason R. Coombs的评论进行了编辑)

import errno
import os

def pid_exists(pid):
    """Check whether pid exists in the current process table.
    UNIX only.
    """
    if pid < 0:
        return False
    if pid == 0:
        # According to "man 2 kill" PID 0 refers to every process
        # in the process group of the calling process.
        # On certain systems 0 is a valid PID but we have no way
        # to know that in a portable fashion.
        raise ValueError('invalid PID 0')
    try:
        os.kill(pid, 0)
    except OSError as err:
        if err.errno == errno.ESRCH:
            # ESRCH == No such process
            return False
        elif err.errno == errno.EPERM:
            # EPERM clearly means there's a process to deny access to
            return True
        else:
            # According to "man 2 kill" possible error values are
            # (EINVAL, EPERM, ESRCH)
            raise
    else:
        return True

如果你使用的是 Windows 操作系统,则无法直接实现此功能,除非你使用 pywin32、ctypes 或者 C 扩展模块。如果可以接受依赖于外部库,则可以使用 psutil

>>> import psutil
>>> psutil.pid_exists(2353)
True

为什么?ESRCH 表示“没有这个进程”。 - Giampaolo Rodolà
2
好的。由于ESRCH表示“没有这样的进程”,errno!= ESRCH表示“不是没有这样的进程”或“进程存在”,这与函数名称非常相似。您特别提到了EPERM,但其他可能的错误代码意味着什么?单独选择一个与检查意图松散相关的错误代码似乎是不正确的,而ESRCH似乎是密切相关的。 - Jason R. Coombs
你是正确的。我编辑了代码,现在反映了你的建议。 - Giampaolo Rodolà
没错,但是上面的代码无论在Python 2还是3中都可以运行。 - Giampaolo Rodolà
@martinkunev:ProcessLookupError继承自OSError - maxschlepzig
显示剩余3条评论

24

如果涉及向进程发送“信号0”的答案仅在该进程归属于运行测试的用户时才有效。否则,即使pid存在于系统中,由于权限问题,您也将收到一个OSError。

为了绕过这个限制,您可以检查/proc/是否存在:

import os

def is_running(pid):
    if os.path.isdir('/proc/{}'.format(pid)):
        return True
    return False

这仅适用于基于Linux的系统,显然。


“PermissionError” 表示 pid 已存在,如果 pid 不存在,则会出现 “ProcessLookupError”。 - jfs
OSError由于权限被拒绝可以通过查看errno或捕获更专业的PermissionError/ProcessLookupError异常来区分其他错误。此外,仅当进程存在时才会收到权限错误。因此,您的示例只是在Linux和其他一些Unix上运行的替代方法,但它并不比正确调用os.kill(pid, 0)更完整。 - maxschlepzig
这个解决方案不是跨平台的。OP希望它能在Unix和Windows上可用。/proc procfs仅存在于Linux上,甚至在BSD或OSX上也不存在。 - C.W.
如果/proc被挂载为hidepid=2,则此方法将失败,如果进程归另一个用户所有,则该进程不会显示在列表中。 - Perkins

11

在Python 3.3及以上版本中,您可以使用异常名称而不是errno常量。 Posix版本:

import os

def pid_exists(pid): 
    if pid < 0: return False #NOTE: pid == 0 returns True
    try:
        os.kill(pid, 0) 
    except ProcessLookupError: # errno.ESRCH
        return False # No such process
    except PermissionError: # errno.EPERM
        return True # Operation not permitted (i.e., process exists)
    else:
        return True # no error, we can send a signal to the process

8

查看这里,以获取获取运行进程及其ID的Windows特定方式。大致如下:

from win32com.client import GetObject
def get_proclist():
    WMI = GetObject('winmgmts:')
    processes = WMI.InstancesOf('Win32_Process')
    return [process.Properties_('ProcessID').Value for process in processes]

你可以将获取到的pid与此列表进行验证。我不清楚性能成本,所以如果你经常进行pid验证,最好先检查一下。
对于*Nix系统,只需使用mluebke的解决方案即可。

这对我很有效。我想检查进程名称,所以将“ProcessID”替换为“Name”,并将其转换为列表检查以返回True或False。 - JamesR

7

在ntrrgc的基础上,我增强了Windows版本,使其检查进程退出代码并检查权限:

def pid_exists(pid):
    """Check whether pid exists in the current process table."""
    if os.name == 'posix':
        import errno
        if pid < 0:
            return False
        try:
            os.kill(pid, 0)
        except OSError as e:
            return e.errno == errno.EPERM
        else:
            return True
    else:
        import ctypes
        kernel32 = ctypes.windll.kernel32
        HANDLE = ctypes.c_void_p
        DWORD = ctypes.c_ulong
        LPDWORD = ctypes.POINTER(DWORD)
        class ExitCodeProcess(ctypes.Structure):
            _fields_ = [ ('hProcess', HANDLE),
                ('lpExitCode', LPDWORD)]

        SYNCHRONIZE = 0x100000
        process = kernel32.OpenProcess(SYNCHRONIZE, 0, pid)
        if not process:
            return False

        ec = ExitCodeProcess()
        out = kernel32.GetExitCodeProcess(process, ctypes.byref(ec))
        if not out:
            err = kernel32.GetLastError()
            if kernel32.GetLastError() == 5:
                # Access is denied.
                logging.warning("Access is denied to get pid info.")
            kernel32.CloseHandle(process)
            return False
        elif bool(ec.lpExitCode):
            # print ec.lpExitCode.contents
            # There is an exist code, it quit
            kernel32.CloseHandle(process)
            return False
        # No exit code, it's running.
        kernel32.CloseHandle(process)
        return True

实际上,根据http://msdn.microsoft.com/en-us/library/windows/desktop/ms683189%28v=vs.85%29.aspx的说明,`GetExistCodeProcess`需要`PROCESS_QUERY_INFORMATION`和`PROCESS_QUERY_LIMITED_INFORMATION`访问权限。 - augustomen
请注意,代码有误。GetExitCodeProcess 接收一个句柄和一个指针,但在此示例中,它将 ExitCodeProcess 结构作为第二个参数接收,而应该只是一个指针。 - Fabio Zadrozny
在使用OpenProcess之后,最好检查GetLastError。如果出现ERROR_ACCESS_DENIED,则表示该进程已存在!以下是使用此功能的完整示例:https://gist.github.com/ociule/8a48d2a6b15f49258a87b5f55be29250 - ociule

4

结合Giampaolo Rodolà关于POSIX的回答我关于Windows的回答,我得到了以下代码:

import os
if os.name == 'posix':
    def pid_exists(pid):
        """Check whether pid exists in the current process table."""
        import errno
        if pid < 0:
            return False
        try:
            os.kill(pid, 0)
        except OSError as e:
            return e.errno == errno.EPERM
        else:
            return True
else:
    def pid_exists(pid):
        import ctypes
        kernel32 = ctypes.windll.kernel32
        SYNCHRONIZE = 0x100000

        process = kernel32.OpenProcess(SYNCHRONIZE, 0, pid)
        if process != 0:
            kernel32.CloseHandle(process)
            return True
        else:
            return False

Windows版本在我的Windows 8.1上无法工作。您必须检查GetExitCodeProcess并确保您有访问权限。 - speedplane
仅使用 kernel32.OpenProcess 是不够的。正如这里所指出的那样,"如果进程最近退出,则 pid 可能仍然存在于句柄中。" 如果 kernel32.OpenProcess 返回非零值,我们仍然需要进一步使用 kernel32.GetExitCodeProcess 来检查退出代码。 - Meow

2
在Windows中,您可以按照以下方式进行操作:
import ctypes
PROCESS_QUERY_INFROMATION = 0x1000
def checkPid(pid):
    processHandle = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_INFROMATION, 0,pid)
    if processHandle == 0:
        return False
    else:
        ctypes.windll.kernel32.CloseHandle(processHandle)
    return True

首先,这段代码尝试获取给定pid的进程句柄。如果句柄有效,则关闭该进程的句柄并返回True;否则返回False。OpenProcess文档:https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320%28v=vs.85%29.aspx


2

例如,如果您想检查banshee音乐播放器是否正在运行,这将适用于Linux……

import subprocess

def running_process(process):
    "check if process is running. < process > is the name of the process."

    proc = subprocess.Popen(["if pgrep " + process + " >/dev/null 2>&1; then echo 'True'; else echo 'False'; fi"], stdout=subprocess.PIPE, shell=True)

    (Process_Existance, err) = proc.communicate()
    return Process_Existance

# use the function
print running_process("banshee")

这种方法与使用 os.kill(pid, 0) 或查看 /proc/{pid} 相比明显劣质。你的代码不是执行一个系统调用,而是fork一个子进程,在该子进程中执行shell,shell解释你多余的小型shell脚本,shell再fork另一个子进程来执行pgrep,最后pgrep迭代 /proc。你的回答并没有回答提问者的问题。OP要求给定PID的方法,而你的方法需要进程名称。 - maxschlepzig

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