【问题标题】:Fast way to determine if a PID exists on (Windows)?确定(Windows)上是否存在PID的快速方法?
【发布时间】:2010-10-10 04:28:33
【问题描述】:

我意识到“快速”有点主观,所以我将结合一些上下文进行解释。我正在开发一个名为 psutil 的 Python 模块,用于以跨平台方式读取进程信息。其中一个函数是 pid_exists(pid) 函数,用于确定 PID 是否在当前进程列表中。

现在我正在以显而易见的方式执行此操作,使用EnumProcesses() 拉取进程列表,然后遍历列表并查找 PID。但是,一些简单的基准测试表明,这比基于 UNIX 的平台(Linux、OS X、FreeBSD)上的 pid_exists 函数要慢得多,我们使用 kill(pid, 0) 和 0 信号来确定 PID 是否存在。额外的测试表明 EnumProcesses 几乎一直在占用。

有谁知道比使用 EnumProcesses 确定 PID 是否存在更快的方法?我尝试了OpenProcess() 并检查打开不存在的进程是否有错误,但结果证明这比遍历 EnumProcesses 列表慢 4 倍以上,所以也解决了。还有其他(更好的)建议吗?

注意:这是一个 Python 库,旨在避免第三方库依赖,如 pywin32 扩展。我需要一个比我们当前代码更快的解决方案,并且不依赖于 pywin32 或标准 Python 发行版中不存在的其他模块。

编辑:澄清一下 - 我们很清楚读取过程信息中存在固有的竞争条件。如果在数据收集过程中流程消失或遇到其他问题,我们会引发异常。 pid_exists() 函数并非旨在取代正确的错误处理。

更新:显然我之前的基准测试存在缺陷 - 我用 C 语言编写了一些简单的测试应用程序,并且 EnumProcesses 始终运行速度较慢并且 OpenProcess(与 GetProcessExitCode 一起使用,以防 PID 有效但进程有停止)实际上是更快而不是更慢。

【问题讨论】:

    标签: python c winapi pid


    【解决方案1】:

    OpenProcess 可以告诉你没有枚举所有。我不知道有多快。

    编辑:请注意,即使您从OpenProcess 获得句柄,您也需要GetExitCodeProcess 来验证进程的状态。

    【讨论】:

    • 事实证明,尽管我之前进行了测试,但毕竟这是更好的方法。有兴趣的可以看我的回答。
    【解决方案2】:

    事实证明,我的基准测试显然存在某种缺陷,因为后来的测试表明 OpenProcess 和 GetExitCodeProcess 毕竟比使用 EnumProcesses 快得多。我不确定发生了什么,但我做了一些新的测试并验证这是更快的解决方案:

    int pid_is_running(DWORD pid)
    {
        HANDLE hProcess;
        DWORD exitCode;
    
        //Special case for PID 0 System Idle Process
        if (pid == 0) {
            return 1;
        }
    
        //skip testing bogus PIDs
        if (pid < 0) {
            return 0;
        }
    
        hProcess = handle_from_pid(pid);
        if (NULL == hProcess) {
            //invalid parameter means PID isn't in the system
            if (GetLastError() == ERROR_INVALID_PARAMETER) { 
                return 0;
            }
    
            //some other error with OpenProcess
            return -1;
        }
    
        if (GetExitCodeProcess(hProcess, &exitCode)) {
            CloseHandle(hProcess);
            return (exitCode == STILL_ACTIVE);
        }
    
        //error in GetExitCodeProcess()
        CloseHandle(hProcess);
        return -1;
    }
    

    请注意,您确实需要使用GetExitCodeProcess(),因为OpenProcess() 将在最近死亡的进程上成功,因此您不能假设有效的进程句柄意味着该进程正在运行。

    另请注意,OpenProcess() 对于任何有效 PID 的 3 以内的 PID 都会成功(请参阅Why does OpenProcess succeed even when I add three to the process ID?

    【讨论】:

    • 感谢最后的提示,我一直在敲桌子,为什么完全不存在的 PID 会返回 true。
    【解决方案3】:

    使用 pid_exists 函数有一个固有的竞争条件:当调用程序开始使用答案时,进程可能已经消失,或者可能已经创建了具有查询 id 的新进程。我敢说,任何使用此功能的应用程序都存在设计缺陷,因此优化此功能是不值得的。

    【讨论】:

    • 是的,任何类似 ps 的应用程序(包括我们的库)都存在固有的竞争条件。但是,此功能仍然有有效的用例。请注意,如果在数据收集过程中的任何时候因为流程消失而失败,我们会引发异常。
    【解决方案4】:

    我会这样编写 Jay 的最后一个函数。

    int pid_is_running(DWORD pid){
        HANDLE hProcess;
        DWORD exitCode;
        //Special case for PID 0 System Idle Process
        if (pid == 0) {
            return 1;
        }
        //skip testing bogus PIDs
        if (pid < 0) {
            return 0;
        }
        hProcess = handle_from_pid(pid);
        if (NULL == hProcess) {
            //invalid parameter means PID isn't in the system
            if (GetLastError() == ERROR_INVALID_PARAMETER) {
                 return 0;
            }
            //some other error with OpenProcess
            return -1;
        }
        DWORD dwRetval = WaitForSingleObject(hProcess, 0);
        CloseHandle(hProcess); // otherwise you'll be losing handles
    
        switch(dwRetval) {
        case WAIT_OBJECT_0;
            return 0;
        case WAIT_TIMEOUT;
            return 1;
        default:
            return -1;
        }
    }
    

    主要区别在于关闭进程句柄(当该函数的客户端长时间运行时很重要)和进程终止检测策略。 WaitForSingleObject 让您有机会等待一段时间(将 0 更改为函数参数值)直到过程结束。

    【讨论】:

    • 我们不想在这种情况下等待(其他函数调用会检测进程是否自行关闭并引发 Python 异常)。但是,关闭进程句柄是对的……我们的“真实”代码确实关闭了句柄,但我在发布的示例中忘记了这样做。
    猜你喜欢
    • 2011-01-17
    • 2013-08-09
    • 1970-01-01
    • 2017-06-07
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    • 2013-03-10
    • 1970-01-01
    相关资源
    最近更新 更多