确定PID是否存在的快速方法(Windows)?

时间:2022-12-04 16:55:49

I realize "fast" is a bit subjective so I'll explain with some context. I'm working on a Python module called psutil for reading process information in a cross-platform way. One of the functions is a pid_exists(pid) function for determining if a PID is in the current process list.

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

Right now I'm doing this the obvious way, using EnumProcesses() to pull the process list, then interating through the list and looking for the PID. However, some simple benchmarking shows this is dramatically slower than the pid_exists function on UNIX-based platforms (Linux, OS X, FreeBSD) where we're using kill(pid, 0) with a 0 signal to determine if a PID exists. Additional testing shows it's EnumProcesses that's taking up almost all the time.

现在我这样做是显而易见的,使用EnumProcesses()来拉动进程列表,然后通过列表进行交互并查找PID。但是,一些简单的基准测试表明,这比基于UNIX的平台(Linux,OS X,FreeBSD)上的pid_exists函数要慢得多,我们在其中使用带有0信号的kill(pid,0)来确定PID是否存在。额外的测试表明它的EnumProcesses几乎一直占据着。

Anyone know a faster way than using EnumProcesses to determine if a PID exists? I tried OpenProcess() and checking for an error opening the nonexistent process, but this turned out to be over 4x slower than iterating through the EnumProcesses list, so that's out as well. Any other (better) suggestions?

任何人都知道比使用EnumProcesses更快的方法来确定PID是否存在?我尝试了OpenProcess()并检查是否有错误打开不存在的进程,但结果比通过EnumProcesses列表迭代慢了4倍,所以也是如此。还有其他(更好的)建议吗?

NOTE: This is a Python library intended to avoid third-party lib dependencies like pywin32 extensions. I need a solution that is faster than our current code, and that doesn't depend on pywin32 or other modules not present in a standard Python distribution.

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

EDIT: To clarify - we're well aware that there are race conditions inherent in reading process iformation. We raise exceptions if the process goes away during the course of data collection or we run into other problems. The pid_exists() function isn't intended to replace proper error handling.

编辑:澄清 - 我们很清楚,阅读过程中存在固有的竞争条件。如果在数据收集过程中进程消失或者遇到其他问题,我们会引发异常。 pid_exists()函数无意替换正确的错误处理。

UPDATE: Apparently my earlier benchmarks were flawed - I wrote some simple test apps in C and EnumProcesses consistently comes out slower and OpenProcess (in conjunction with GetProcessExitCode in case the PID is valid but the process has stopped) is actually much faster not slower.

更新:显然我早期的基准测试存在缺陷 - 我在C中编写了一些简单的测试应用程序,EnumProcesses的编写速度一直较慢,OpenProcess(与PID有效但是进程停止时的GetProcessExitCode结合使用)实际上要快得多而且速度慢。

4 个解决方案

#1


8  

OpenProcess could tell you w/o enumerating all. I have no idea how fast.

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

EDIT: note that you also need GetExitCodeProcess to verify the state of the process even if you get a handle from OpenProcess.

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

#2


4  

There is an inherent race condition in the use of pid_exists function: by the time the calling program gets to use the answer, the process may have already disappeared, or a new process with the queried id may have been created. I would dare say that any application that uses this function is flawed by design and that optimizing this function is therefore not worth the effort.

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

#3


3  

Turns out that my benchmarks evidently were flawed somehow, as later testing reveals OpenProcess and GetExitCodeProcess are much faster than using EnumProcesses after all. I'm not sure what happened but I did some new tests and verified this is the faster solution:

事实证明我的基准测试显然是有缺陷的,因为后来的测试显示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;
}

Note that you do need to use GetExitCodeProcess() because OpenProcess() will succeed on processes that have died recently so you can't assume a valid process handle means the process is running.

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

Also note that OpenProcess() succeeds for PIDs that are within 3 of any valid PID (See Why does OpenProcess succeed even when I add three to the process ID?)

另请注意,OpenProcess()成功处于任何有效PID的3之内的PID(请参阅为什么OpenProcess成功,即使我将3添加到进程ID?)

#4


3  

I'd code Jay's last function this way.

我用这种方式编码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;
    }
}

The main difference is closing the process handle (important when the client of this function is running for a long time) and the process termination detection strategy. WaitForSingleObject gives you the opportunity to wait for a while (changing the 0 to a function parameter value) until the process ends.

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

#1


8  

OpenProcess could tell you w/o enumerating all. I have no idea how fast.

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

EDIT: note that you also need GetExitCodeProcess to verify the state of the process even if you get a handle from OpenProcess.

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

#2


4  

There is an inherent race condition in the use of pid_exists function: by the time the calling program gets to use the answer, the process may have already disappeared, or a new process with the queried id may have been created. I would dare say that any application that uses this function is flawed by design and that optimizing this function is therefore not worth the effort.

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

#3


3  

Turns out that my benchmarks evidently were flawed somehow, as later testing reveals OpenProcess and GetExitCodeProcess are much faster than using EnumProcesses after all. I'm not sure what happened but I did some new tests and verified this is the faster solution:

事实证明我的基准测试显然是有缺陷的,因为后来的测试显示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;
}

Note that you do need to use GetExitCodeProcess() because OpenProcess() will succeed on processes that have died recently so you can't assume a valid process handle means the process is running.

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

Also note that OpenProcess() succeeds for PIDs that are within 3 of any valid PID (See Why does OpenProcess succeed even when I add three to the process ID?)

另请注意,OpenProcess()成功处于任何有效PID的3之内的PID(请参阅为什么OpenProcess成功,即使我将3添加到进程ID?)

#4


3  

I'd code Jay's last function this way.

我用这种方式编码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;
    }
}

The main difference is closing the process handle (important when the client of this function is running for a long time) and the process termination detection strategy. WaitForSingleObject gives you the opportunity to wait for a while (changing the 0 to a function parameter value) until the process ends.

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