はじめに
PHPでマルチプロセス処理を実装していると、子プロセスがどのように終了したのかを把握する必要が出てきます。今回は、子プロセスがシグナルによって終了したかどうかを判定するpcntl_wifsignaled関数について、実践的な例を交えながら詳しく解説します。
pcntl_wifsignaled関数とは?
pcntl_wifsignaled()
は、子プロセスがシグナルを受信して終了したかどうかを判定するPHP関数です。プロセスの終了ステータスを解析し、異常終了(シグナル終了)だったかを真偽値で返します。
基本構文
pcntl_wifsignaled(int $status): bool
パラメータ:
$status
:pcntl_waitpid()
やpcntl_wait()
で取得したステータス値
戻り値:
true
: 子プロセスがシグナルによって終了したfalse
: 子プロセスが正常終了またはシグナル以外で終了した
なぜこの関数が必要なのか?
Linuxなどのシステムでは、プロセスは主に2つの方法で終了します:
- 正常終了: プログラムが
exit()
を呼び出して自然に終了 - シグナル終了: 外部からシグナル(SIGKILL、SIGTERMなど)を受信して強制終了
親プロセスが子プロセスを適切に管理するには、どちらの方法で終了したのかを知る必要があります。pcntl_wifsignaled()
は、シグナル終了を検出するための専用関数です。
実践的なコード例
基本的な使用例
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die("フォークに失敗しました\n");
} elseif ($pid == 0) {
// 子プロセス
echo "子プロセス開始(PID: " . getmypid() . ")\n";
sleep(10); // 10秒待機(この間にシグナルを受信する可能性)
exit(0); // 正常終了
} else {
// 親プロセス
echo "親プロセス: 子プロセス(PID: $pid)を生成しました\n";
// 子プロセスの終了を待機
pcntl_waitpid($pid, $status);
// 終了方法を判定
if (pcntl_wifsignaled($status)) {
$signal = pcntl_wtermsig($status);
echo "子プロセスはシグナル $signal によって終了しました\n";
} else {
echo "子プロセスは正常に終了しました\n";
}
}
シグナルハンドリングと組み合わせた例
<?php
// シグナルハンドラの設定
pcntl_signal(SIGTERM, function($signo) {
echo "子プロセス: SIGTERMを受信しました\n";
exit(1);
});
$pid = pcntl_fork();
if ($pid == 0) {
// 子プロセス
echo "子プロセス開始\n";
while (true) {
pcntl_signal_dispatch(); // シグナルを処理
sleep(1);
}
} else {
// 親プロセス
sleep(2);
// 子プロセスにSIGTERMを送信
echo "親プロセス: 子プロセスにSIGTERMを送信します\n";
posix_kill($pid, SIGTERM);
pcntl_waitpid($pid, $status);
if (pcntl_wifsignaled($status)) {
$signal = pcntl_wtermsig($status);
echo "シグナル終了を検出: シグナル番号 $signal\n";
// シグナル名を取得
$signals = [
SIGTERM => 'SIGTERM',
SIGKILL => 'SIGKILL',
SIGINT => 'SIGINT'
];
if (isset($signals[$signal])) {
echo "シグナル名: {$signals[$signal]}\n";
}
} elseif (pcntl_wifexited($status)) {
$exitcode = pcntl_wexitstatus($status);
echo "正常終了: 終了コード $exitcode\n";
}
}
関連する重要な関数
pcntl_wifsignaled()
は、通常以下の関数と組み合わせて使用します:
1. pcntl_wtermsig()
シグナル終了した場合に、どのシグナルで終了したかを取得します。
if (pcntl_wifsignaled($status)) {
$signal = pcntl_wtermsig($status);
echo "終了シグナル: $signal\n";
}
2. pcntl_wifexited()
プロセスが正常終了したかどうかを判定します。
if (pcntl_wifexited($status)) {
echo "正常終了しました\n";
}
3. pcntl_waitpid()
子プロセスの終了を待機し、ステータスを取得します。
pcntl_waitpid($pid, $status);
実用的な応用例: ワーカープロセス管理
<?php
class ProcessManager
{
private $workers = [];
public function spawnWorker($id)
{
$pid = pcntl_fork();
if ($pid == -1) {
throw new Exception("フォークに失敗しました");
} elseif ($pid == 0) {
// 子プロセス(ワーカー)
$this->doWork($id);
exit(0);
} else {
// 親プロセス
$this->workers[$pid] = ['id' => $id, 'started' => time()];
echo "ワーカー $id (PID: $pid) を起動しました\n";
}
}
private function doWork($id)
{
echo "ワーカー $id: 処理を開始します\n";
// 実際の処理をシミュレート
for ($i = 0; $i < 10; $i++) {
sleep(1);
pcntl_signal_dispatch();
}
echo "ワーカー $id: 処理を完了しました\n";
}
public function waitForWorkers()
{
while (count($this->workers) > 0) {
$pid = pcntl_waitpid(-1, $status);
if ($pid > 0) {
$worker = $this->workers[$pid];
if (pcntl_wifsignaled($status)) {
$signal = pcntl_wtermsig($status);
echo "警告: ワーカー {$worker['id']} (PID: $pid) がシグナル $signal で異常終了しました\n";
// 必要に応じて再起動
$this->spawnWorker($worker['id']);
} elseif (pcntl_wifexited($status)) {
$exitcode = pcntl_wexitstatus($status);
echo "ワーカー {$worker['id']} (PID: $pid) が正常終了しました (終了コード: $exitcode)\n";
}
unset($this->workers[$pid]);
}
}
}
}
// 使用例
$manager = new ProcessManager();
// 3つのワーカーを起動
for ($i = 1; $i <= 3; $i++) {
$manager->spawnWorker($i);
}
// すべてのワーカーの終了を待機
$manager->waitForWorkers();
よくある使用シーン
1. タイムアウト処理
子プロセスが一定時間内に終了しない場合、SIGKILLで強制終了させる:
$pid = pcntl_fork();
if ($pid == 0) {
// 長時間かかる処理
sleep(100);
exit(0);
} else {
$timeout = 5;
$start = time();
while (time() - $start < $timeout) {
$result = pcntl_waitpid($pid, $status, WNOHANG);
if ($result == $pid) {
// 子プロセスが終了した
break;
}
sleep(1);
}
// タイムアウトした場合
if (pcntl_waitpid($pid, $status, WNOHANG) == 0) {
echo "タイムアウト: 子プロセスを強制終了します\n";
posix_kill($pid, SIGKILL);
pcntl_waitpid($pid, $status);
}
if (pcntl_wifsignaled($status)) {
echo "子プロセスはシグナルで終了しました\n";
}
}
2. クラッシュ検出とログ記録
pcntl_waitpid($pid, $status);
if (pcntl_wifsignaled($status)) {
$signal = pcntl_wtermsig($status);
// ログに記録
error_log("プロセス $pid がシグナル $signal でクラッシュしました");
// アラートを送信
if ($signal == SIGSEGV) {
error_log("セグメンテーション違反が発生しました!");
}
}
注意点とベストプラクティス
1. Linux/Unix環境でのみ利用可能
pcntl_wifsignaled()
はPOSIX準拠システムでのみ動作します。Windows環境では使用できません。
if (function_exists('pcntl_wifsignaled')) {
// pcntl関数が利用可能
} else {
die("この環境ではpcntl関数が利用できません\n");
}
2. 必ずpcntl_waitpid()の後に使用
ステータス値はpcntl_waitpid()
またはpcntl_wait()
で取得する必要があります。
3. 他の終了判定関数と組み合わせる
完全な終了状態の把握には、複数の判定関数を使用します:
pcntl_waitpid($pid, $status);
if (pcntl_wifsignaled($status)) {
// シグナル終了
$signal = pcntl_wtermsig($status);
} elseif (pcntl_wifexited($status)) {
// 正常終了
$exitcode = pcntl_wexitstatus($status);
} elseif (pcntl_wifstopped($status)) {
// 停止中(WUNTRACEDオプション使用時)
$signal = pcntl_wstopsig($status);
}
まとめ
pcntl_wifsignaled()
は、PHPでマルチプロセスプログラミングを行う際に欠かせない関数です。以下のポイントを押さえておきましょう:
- 目的: 子プロセスがシグナルで終了したかを判定
- 使用場面: プロセス監視、クラッシュ検出、エラーハンドリング
- 組み合わせ:
pcntl_wtermsig()
でシグナル番号を取得 - 注意: Linux/Unix環境専用、
pcntl_waitpid()
と併用必須
適切なプロセス管理により、安定したマルチプロセスアプリケーションを構築できます。ぜひ実際のプロジェクトで活用してみてください!