检测进程是否为 运行 的最快方法

Fastest way to detect if a process is running

我需要我的对象“Launcher”来检测它的关联进程是否 运行ning。 我最初的解决方案是使用 psutil

简单地 运行 一个 for 循环
def Launcher(self, processName):
    self.processName=processName

def process_up(self, attempts=0):
        if attempts <= 3:
            try:
                if self.processName in (p.name() for p in psutil.process_iter()):
                    return True
                else:
                    return False
                
            except:
                self.process_up(attempts=1)
        else:
            logging.error("Psutil Fatal Error. Unable to check status of process {}".format(self.processName))
            return False

递归用于在 for 循环中检测到进程 p 但在调用 .name() 之前死亡的极少数情况。

无论如何,在我用我的所有进程(大约 40 个进程,所以 40 个启动器 运行ning)测试它之前,这一切听起来都很好而且花花公子,问题是 运行ning 这个循环大约需要 0.1 秒,总计约 4 秒。

但是,我需要瞄准 <1 秒。还有哪些超快速的方法可以确定给定进程是否 运行ning?我不需要知道关于进程的任何信息(我不关心它的 pid 或名称),只关心它是否启动。

附带说明:我不能使用多线程或任何类型的并行性。我必须按顺序 运行 这些启动器。

编辑:我也尝试了以下代码,这在性能方面绝对更好:

def process_up(self):
    try:
        call = subprocess.check_output("pgrep -f '{}'".format(self.processName), shell=True)
        return True
    except subprocess.CalledProcessError:
        return False

现在代码 运行s 在 ~2 秒内完成,但仍然太多了

使用 pidof 应该比 pgrep

更快
def process_up(self):
    try:
        call = subprocess.check_output("pidof '{}'".format(self.processName), shell=True)
        return True
    except subprocess.CalledProcessError:
        return False

根据 psutil doc,如果使用默认参数调用 process_iter() 会很慢。

这里您需要的是提供您需要的attributes列表。

例如你可以替换:

psutil.process_iter())

作者:

psutil.process_iter(['name','pid']))

此外,您可以像这样存储 pid:

if my_pid in psutil.pids():
  # process still alive

如果您关心速度,为什么不使用 os 模块? os 模块提供最低级别的进程例程,并提供比调用 shell 可执行文件最佳的性能。如果您想要更高级别的东西,请考虑 subprocess 模块。

import os
import sys
from time import sleep

def child_process():
    """
    Run your child process here
    """
    print("child process started..")
    sys.stdout.flush()
    # put your external process here
    sleep(10)
    os.execv("/bin/echo", ["I am a child process"])
    
    # should never get here!
    print("I am in a bad place! - child")
    os._exit(os.EX_SOFTWARE)

def process_up(pid):
    """
    Is the process up?
    :return: True if process is up
    """
    try:
        return None != os.waitpid(pid, os.WNOHANG)
    except ChildProcessError: # no child processes
        return False

def main():
    pid = os.fork()
    if pid == 0:
        child_process()
    else:
        # main thread
        # ...
        checks=0
        while process_up(pid):
             checks += 1
        print(f"Main: Process {pid} completed. {checks} checks in 10 seconds")
        # Example output:
        # Process 370 completed. 13307171 checks in 10 seconds.

if __name__ == "__main__":
    main()

一个选择是集中检查,这样您就可以针对所有内容检查 运行 个进程列表一次,而不是每个受监控的进程一次。

monitored_processes = set(...)  # list of names of processes
running_processes = set(p.name() for p in psutil.process_iter())
missing_processes = monitored_processes - running_processes

您需要一些额外的代码来重试,但这也不难:

missing_counts = Counter()

monitored_processes = set(...)  # list of names of processes

while True:
    running_processes = {p.name() for p in psutil.process_iter()}
    missing_processes = monitored_processes - running_processes

    for recovered_process in missing_counts.keys() - missing_processes:
        del missing_counts[recovered_process]

    for missing_process in missing_processes:
        missing_counts[missing_process] += 1

    down_processes = {
        down_process
        for down_process, count in missing_counts.items()
        if count > 3
    }

我不确定这是否是 Windows 的特定问题。 psutil 在我的机器上比 subprocess + tasklist.

慢 2 倍以上

通常我的系统上有大约 400 个进程 运行ning。如果我想检查 Elite Dangerous 的启动器是否 运行ning,我有以下代码:

t0 = time.perf_counter()
st = 'EDLaunch.exe' in (p.name() for p in psutil.process_iter())
print(time.perf_counter() - t0)
print(st)

t0 = time.perf_counter()
s = subprocess.check_output('tasklist', shell=True).decode()
st = 'EDLaunch.exe' in s
print(time.perf_counter() - t0)
print(st)

当启动器不是运行ning时,输出是

0.8039536
False
0.3197087999999999
False

子进程要快得多。此外,使用循环而不是列表理解对于 psutil 来说不会更快。

其实还有更好的办法from here。 由于我们的目标是检查进程是否 运行ning,因此返回列表并进行检查没有意义。 所以我可以做到

t0 = time.perf_counter()
s = subprocess.check_output('tasklist /NH /FI "IMAGENAME eq EDLaunch.exe"', shell=True).decode()
st = 'EDLaunch.exe' in s
print(time.perf_counter() - t0)
print(st)

需要 0.1 秒才能完成。再次比简单的子流程方式快 3 倍。

唯一的缺点是,当进程未 运行ning 时,返回的消息类似于 INFO: No tasks are running which match the specified criteria.,它与系统区域设置一致....您可能会以其他语言获得此输出如果您的 Windows 语言不是英语。

幸运的是,您可以 运行 命令 chcp 437 将输出更改为英语,而不必每次都 运行 运行 check_output。只需在程序开始时执行一次即可。

所以我现在的方式是这样的

# make sure the output is in English
# this line only need to run once (os.system is also slower than subprocess somehow)
subprocess.run('chcp 437', shell=True)
s = subprocess.check_output('tasklist /NH /FI "IMAGENAME eq EDLaunch.exe"', shell=True).decode()