我意识到“快”有点主观,所以我会用一些上下文来解释。我正在开发一个名为 psutil 的 Python 模块,用于以跨平台方式读取进程信息。其中一个函数是
pid_exists(pid)
函数,用于确定 PID 是否在当前进程列表中。
现在我正在以明显的方式执行此操作,使用 EnumProcesses() 来提取进程列表,然后通过列表进行交互并查找 PID。然而,一些简单的基准测试表明,这比基于 UNIX 的平台(Linux、OS X、FreeBSD)上的 pid_exists 函数慢得多,在这些平台上,我们使用带有 0 信号的
kill(pid, 0)
来确定 PID 是否存在。额外的测试表明 EnumProcesses 几乎一直占用着时间。
有人知道比使用 EnumProcesses 更快的方法来确定 PID 是否存在吗?我尝试了 OpenProcess() 并检查打开不存在进程的错误,但这比迭代 EnumProcesses 列表慢 4 倍以上,所以这也是问题。还有其他(更好的)建议吗?
注意:这是一个 Python 库,旨在避免第三方库依赖项,例如 pywin32 扩展。我需要一个比我们当前代码更快的解决方案,并且不依赖于 pywin32 或标准 Python 发行版中不存在的其他模块。
编辑:澄清一下 - 我们很清楚读取过程信息中存在固有的竞争条件。如果该过程在数据收集过程中消失或遇到其他问题,我们会提出异常。 pid_exists() 函数并不是为了取代正确的错误处理。
更新:显然我之前的基准测试是有缺陷的 - 我用 C 和 EnumProcesses 编写了一些简单的测试应用程序,结果始终较慢,而 OpenProcess(与 GetProcessExitCode 结合使用,以防 PID 有效但进程已停止)实际上要快得多 不慢。
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()
会在最近死亡的进程上成功,因此您不能假设有效的进程句柄意味着该进程正在运行。另请注意,对于任何有效 PID 的 3 以内的 PID,
OpenProcess()
都会成功(请参阅为什么即使我向进程 ID 添加 3,OpenProcess 也会成功?)
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 更改为函数参数值),直到进程结束。