PID в языке PHP: получение и управление процессами
Идентификатор процесса (PID) в PHP: основные подходы
Основной способ получить идентификатор текущего процесса в PHP - использовать функцию getmypid(). Она доступна во всех версиях PHP и не требует дополнительных расширений. Функция возвращает целое число - PID процесса, в котором выполняется скрипт.
<?php
$pid = getmypid();
echo "Текущий PID: " . $pid;
?>
Текущий PID: 31415
Возможные проблемы:
- При работе через веб-сервер (например, Apache с mod_php) несколько запросов могут выполняться в одном и том же процессе, поэтому PID не уникален для каждого запроса. В CLI-режиме каждый запуск даёт новый PID.
- Функция getmypid() возвращает PID только текущего процесса. Для получения PID родительского процесса нужно использовать posix_getppid().
Как узнать идентификатор родительского процесса?
Для получения PID родительского процесса применяется функция posix_getppid(). Она доступна, если установлено расширение POSIX (обычно включено по умолчанию в CLI).
<?php
if (function_exists('posix_getppid')) {
$ppid = posix_getppid();
echo "PID родительского процесса: " . $ppid;
} else {
echo "Расширение POSIX не установлено.";
}
?>
PID родительского процесса: 1
Типичные ошибки:
- Функция posix_getppid() отсутствует, если PHP собран без расширения POSIX. Решение: установить пакет php-posix или пересобрать PHP с поддержкой POSIX.
- В веб-окружении (например, под Apache с mod_php) родительским процессом может быть серверный процесс, а не PID родительского скрипта.
Как получить PID через командную оболочку, если функции PHP недоступны?
Иногда возникают ситуации, когда стандартные функции не работают (например, в окружении с ограничениями). Альтернативный способ - выполнить команду оболочки echo $$. Однако этот метод работает только в CLI-режиме и не всегда корректен.
<?php
$pid = trim(shell_exec('echo $$'));
echo "PID через shell: " . $pid;
?>
PID через shell: 31415
Проблемы и ограничения:
- Использование shell_exec() требует отключения директивы shell_exec в php.ini, если она запрещена.
- В веб-окружении переменная $$ возвращает PID интерпретатора оболочки, запущенного для выполнения команды, а не PHP-процесса. Результат может быть неверным.
- Команда может быть заблокирована в безопасном режиме (deprecated).
Как получить PID дочернего процесса после создания форка?
При работе с многозадачностью через pcntl_fork() родительский процесс получает PID дочернего как возвращаемое значение. Дочерний процесс получает 0. Пример:
<?php
$child_pid = pcntl_fork();
if ($child_pid == -1) {
die('Ошибка при создании процесса');
} elseif ($child_pid) {
// в родительском процессе
echo "Родитель. PID дочернего: " . $child_pid;
} else {
// в дочернем процессе
echo "Дочерний. Мой PID: " . getmypid();
exit;
}
?>
Родитель. PID дочернего: 12345
Частые ошибки:
- Расширение pcntl доступно только в CLI-режиме и не работает в веб-окружении. Попытка использования вызовет ошибку.
- После вызова pcntl_fork() обе ветки продолжают выполнение. Необходимо правильно разделить логику, иначе код может выполниться дважды.
- Для избежания зомби-процессов родитель должен вызвать pcntl_wait().
Как проверить, существует ли процесс с заданным PID?
Проверка существования процесса выполняется с помощью функции posix_kill() с сигналом 0. Сигнал 0 не отправляется процессу, но проверяет возможность отправки. Если процесс существует, функция возвращает true.
<?php
$pid = 12345;
if (@posix_kill($pid, 0)) {
echo "Процесс с PID $pid существует.";
} else {
echo "Процесс с PID $pid не найден.";
}
?>
Процесс с PID 12345 существует.
Потенциальные проблемы:
- Проверка с сигналом 0 не требует прав на отправку сигнала, но требует права на определение наличия процесса. Для процессов других пользователей может вернуть false.
- Функция posix_kill() генерирует предупреждение, если процесс не существует. Рекомендуется подавлять ошибки с помощью оператора @.
- Метод не подходит для проверки процессов в системе, которые не принадлежат текущему пользователю, без соответствующих прав.
Как завершить процесс по PID?
Принудительное завершение процесса выполняется с помощью posix_kill() с сигналом SIGKILL (9). Альтернативно можно использовать команду kill -9 через shell_exec.
<?php
$pid = 12345;
if (posix_kill($pid, 9)) {
echo "Процесс $pid завершен.";
} else {
echo "Не удалось завершить процесс $pid.";
}
?>
Процесс 12345 завершен.
Риски и предостережения:
- Завершение процесса работает только если PHP-скрипт выполняется от имени того же пользователя, что и целевой процесс, или от root.
- Применение SIGKILL не даёт процессу корректно завершиться, возможна потеря данных. Рекомендуется сначала отправлять SIGTERM (15) и при отсутствии реакции - SIGKILL.
- Ошибка "Operation not permitted" означает недостаток прав.
Расширенные примеры работы с PID в PHP
Создание нескольких дочерних процессов и управление ими
В следующем примере родительский процесс порождает трёх дочерних процессов, каждый из которых выводит свой PID и случайное число. Родитель ожидает завершения всех потомков через pcntl_wait().
<?php
for ($i = 1; $i <= 3; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
die("Невозможно создать процесс");
} elseif ($pid) {
// родитель продолжает цикл
} else {
// дочерний процесс
echo "Дочерний $i: PID " . getmypid() . ", случайное число " . rand(1,100) . PHP_EOL;
exit; // важно завершить дочерний процесс
}
}
// Родитель ожидает всех детей
while (true) {
$status = null;
$pid = pcntl_wait($status);
if ($pid == -1) break;
echo "Завершился дочерний процесс с PID $pid" . PHP_EOL;
}
?>
Дочерний 1: PID 20001, случайное число 42 Дочерний 2: PID 20002, случайное число 73 Дочерний 3: PID 20003, случайное число 15 Завершился дочерний процесс с PID 20001 Завершился дочерний процесс с PID 20002 Завершился дочерний процесс с PID 20003
Создание демона с записью PID в файл
Демон обычно записывает свой PID в файл, чтобы другие процессы могли управлять им (например, завершить). Пример демона с циклической работой и сохранением PID:
<?php
// Файл для хранения PID
$pidFile = '/var/run/myapp.pid';
// Первый fork: отсоединение от терминала
$pid = pcntl_fork();
if ($pid == -1) {
die('Ошибка первого форка');
} elseif ($pid) {
// Родитель завершается
exit;
}
// Становимся лидером сессии
posix_setsid();
// Второй fork: гарантирует, что не станет лидером сессии
$pid = pcntl_fork();
if ($pid == -1) {
die('Ошибка второго форка');
} elseif ($pid) {
// Первый дочерний процесс завершается, основной остается
exit;
}
// Теперь это демон
$currentPid = getmypid();
file_put_contents($pidFile, $currentPid);
echo "Демон запущен с PID $currentPid" . PHP_EOL;
// Бесконечный цикл работы демона
while (true) {
// полезная работа
sleep(60);
}
?>
Демон запущен с PID 30001
Использование proc_open и proc_get_status для внешней команды
Функция proc_open() позволяет выполнить внешнюю программу и получить её PID через proc_get_status(). Пример запуска утилиты sleep в фоне:
<?php
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w')
);
$process = proc_open('sleep 10', $descriptorspec, $pipes);
if (is_resource($process)) {
$status = proc_get_status($process);
$pid = $status['pid'];
echo "Внешний процесс запущен с PID: " . $pid . PHP_EOL;
// Можно проверить статус позже
sleep(5);
$status = proc_get_status($process);
if ($status['running']) {
echo "Процесс все еще работает." . PHP_EOL;
} else {
echo "Процесс завершен." . PHP_EOL;
}
// Закрыть процесс
proc_close($process);
} else {
echo "Ошибка запуска процесса.";
}
?>
Внешний процесс запущен с PID: 40001 Процесс все еще работает.
Проверка существования процесса через цикл с таймаутом
Иногда необходимо дождаться завершения другого процесса, проверяя его статус периодически. Пример проверки существования процесса с PID 10000 каждые 2 секунды в течение 30 секунд:
<?php
$targetPid = 10000;
$timeout = 30;
$start = time();
while (time() - $start < $timeout) {
if (@!posix_kill($targetPid, 0)) {
echo "Процесс $targetPid завершился." . PHP_EOL;
break;
}
echo "Еще работает..." . PHP_EOL;
sleep(2);
}
if (time() - $start >= $timeout) {
echo "Таймаут: процесс $targetPid не завершился." . PHP_EOL;
}
?>
Еще работает... Еще работает... Процесс 10000 завершился.