2009-02-26 2 views
8

Я понимаю, что «быстрый» немного субъективен, поэтому я объясню в некотором контексте. Я работаю над модулем Python под названием psutil для чтения информации о процессе кросс-платформенным способом. Одной из функций является функция pid_exists(pid) для определения того, находится ли PID в текущем списке процессов.Быстрый способ определить, существует ли PID (Windows)?

Прямо сейчас я делаю это очевидным образом, используя EnumProcesses(), чтобы вытащить список процессов, затем перейдя через список и ищем ПИД. Однако некоторые простые тесты показывают, что это значительно медленнее, чем функция pid_exists на платформах на базе UNIX (Linux, OS X, FreeBSD), где мы используем kill(pid, 0) с сигналом 0, чтобы определить, существует ли PID. Дополнительное тестирование показывает, что EnumProcesses занимает почти все время.

Кто-нибудь знает более быстрый способ, чем использование EnumProcesses, чтобы определить, существует ли PID? Я попробовал OpenProcess() и проверил ошибку, открыв несуществующий процесс, но это оказалось более чем на 4 раза медленнее, чем повторение в списке EnumProcesses, так что это тоже не так. Любые другие (лучшие) предложения?

ПРИМЕЧАНИЕ: Это библиотека Python, предназначенная для избежания зависимостей сторонних библиотек, таких как расширения pywin32. Мне нужно решение, которое быстрее, чем наш текущий код, и это не зависит от pywin32 или других модулей, которые не присутствуют в стандартном дистрибутиве Python.

EDIT: Чтобы уточнить, мы хорошо знаем, что существуют условия гонки, присущие процессу чтения. Мы поднимаем исключения, если процесс уходит во время сбора данных или мы сталкиваемся с другими проблемами. Функция pid_exists() не предназначена для замены правильной обработки ошибок.

UPDATE: Видимо, мои ранние тесты были ошибочными - я написал несколько простых приложений тестирования в C и EnumProcesses последовательно выходит медленнее и OpenProcess (в сочетании с GetProcessExitCode в случае PID действителен, но процесс остановился) на самом деле много быстрее не медленнее.

ответ

8

OpenProcess мог бы рассказать вам, не перечисляя все. Я не знаю, как быстро.

EDIT: обратите внимание, что вы также должны GetExitCodeProcess проверить состояние процесса, даже если вы получите ручку от OpenProcess.

+0

Оказывается, несмотря на мое раннее тестирование, это лучший способ пойти в конце концов. См. Мой ответ для деталей, если вы заинтересованы. – Jay

4

При использовании функции pid_exists существует неотъемлемое условие гонки: к моменту, когда вызывающая программа получает ответ, процесс, возможно, уже исчез или новый процесс с запрошенным идентификатором может быть создан. Я бы посмел сказать, что любое приложение, использующее эту функцию, является ошибочным по дизайну, и поэтому оптимизация этой функции не стоит усилий.

+0

Да, в любом ps-подобном приложении есть встроенное условие гонки, включая нашу библиотеку. Однако эта функция все еще имеет допустимые варианты использования. Обратите внимание, что мы * также * поднимаем исключения, если в какой-либо момент процесса сбора данных не удается, потому что процесс исчез. – Jay

3

Оказывается, мои тесты, очевидно, были как-то испорчены, так как последующее тестирование показывает, что 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() успешно для PIDs, которые находятся в пределах 3 любого действительного PID (см Why does OpenProcess succeed even when I add three to the process ID?)

+0

Спасибо за последнее замечание, я ударился головой в стол о том, почему полностью несуществующий PID вернется. –

3

Я последний код функции Джея этот путь.

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 на значение параметра функции), пока процесс не завершится.

+0

Мы не хотим ждать в этом случае (другие вызовы функций обнаружат, завершил ли процесс сам процесс и создал исключение для Python). Но вы правы в закрытии обработчиков процессов ... наш «настоящий» код закрывает дескрипторы, но я забыл сделать это в примере, который я разместил. – Jay