こんにちは!今回はPHPの高度なシグナル制御機能であるpcntl_sigprocmask関数について詳しく解説します。クリティカルセクションでシグナルを一時的にブロックしたい、より精密なプロセス制御が必要な方は必見です!
pcntl_sigprocmask関数とは?
pcntl_sigprocmask()は、プロセスのシグナルマスク(どのシグナルをブロックするか)を設定・取得する関数です。シグナルマスクを使うことで、特定のシグナルを一時的にブロック(遅延)したり、ブロックを解除したりできます。
基本構文
pcntl_sigprocmask(int $mode, array $signals, array &$old_signals = null): bool
- $mode: 操作モード(SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK)
- $signals: 対象となるシグナルの配列
- $old_signals: 以前のシグナルマスクを格納する配列(参照渡し)
- 戻り値: 成功時true、失敗時false
操作モードの種類
| モード | 定数 | 説明 | 
|---|---|---|
| ブロック | SIG_BLOCK | 指定したシグナルをマスクに追加(ブロック) | 
| 解除 | SIG_UNBLOCK | 指定したシグナルをマスクから削除(許可) | 
| 設定 | SIG_SETMASK | シグナルマスクを指定した内容で完全に置き換え | 
なぜシグナルマスクが必要なのか?
使用シーン
- クリティカルセクションの保護
- データベース更新中にシグナルで中断されたくない
- ファイル書き込み中の割り込みを防ぎたい
 
- アトミックな操作
- 一連の処理を途中で中断させたくない
 
- シグナルのタイミング制御
- 特定の処理が完了するまでシグナルを遅延させる
 
- デッドロックの回避
- シグナルハンドラ内での競合状態を防ぐ
 
実践的なコード例
基本的な使い方:シグナルをブロック
<?php
$running = true;
pcntl_signal(SIGINT, function() use (&$running) {
    echo "\nSIGINTを受信しました。終了します。\n";
    $running = false;
});
echo "プログラム開始\n";
while ($running) {
    echo "通常処理中... (Ctrl+Cで終了可能)\n";
    sleep(1);
    pcntl_signal_dispatch();
    
    // クリティカルセクション開始
    echo "\n=== クリティカルセクション開始 ===\n";
    
    // SIGINTをブロック
    pcntl_sigprocmask(SIG_BLOCK, [SIGINT], $old_mask);
    echo "SIGINT をブロックしました (Ctrl+C は無効)\n";
    
    // 重要な処理(中断されたくない)
    for ($i = 1; $i <= 5; $i++) {
        echo "重要な処理 {$i}/5...\n";
        sleep(1);
        // この間Ctrl+Cを押してもすぐには反応しない
    }
    
    // SIGINTのブロックを解除
    pcntl_sigprocmask(SIG_UNBLOCK, [SIGINT]);
    echo "SIGINT のブロックを解除しました\n";
    echo "=== クリティカルセクション終了 ===\n\n";
    
    // ブロック中に受信したシグナルがここで処理される
    pcntl_signal_dispatch();
}
echo "プログラム終了\n";
?>
データベース更新の保護
<?php
class DatabaseTransaction {
    private $running = true;
    private $blocked_signals = [];
    
    public function __construct() {
        pcntl_signal(SIGTERM, [$this, 'handleShutdown']);
        pcntl_signal(SIGINT, [$this, 'handleShutdown']);
    }
    
    public function handleShutdown($signal) {
        echo "\n終了シグナルを受信。トランザクション完了後に終了します。\n";
        $this->running = false;
    }
    
    public function blockSignals() {
        $signals = [SIGTERM, SIGINT, SIGHUP];
        
        if (pcntl_sigprocmask(SIG_BLOCK, $signals, $this->blocked_signals)) {
            echo "[シグナル] ブロックしました: " . implode(', ', $signals) . "\n";
            return true;
        }
        return false;
    }
    
    public function unblockSignals() {
        if (pcntl_sigprocmask(SIG_SETMASK, $this->blocked_signals)) {
            echo "[シグナル] ブロックを解除しました\n";
            return true;
        }
        return false;
    }
    
    public function executeTransaction() {
        echo "トランザクション開始\n";
        
        // シグナルをブロック
        $this->blockSignals();
        
        try {
            // BEGIN
            echo "  - データベース接続\n";
            sleep(1);
            
            echo "  - データ更新中 (1/3)\n";
            sleep(1);
            
            echo "  - データ更新中 (2/3)\n";
            sleep(1);
            
            echo "  - データ更新中 (3/3)\n";
            sleep(1);
            
            // COMMIT
            echo "  - コミット実行\n";
            sleep(1);
            
            echo "トランザクション完了\n";
            
        } finally {
            // 必ずシグナルのブロックを解除
            $this->unblockSignals();
            pcntl_signal_dispatch();
        }
    }
    
    public function run() {
        echo "データベースワーカー起動: PID=" . getmypid() . "\n";
        
        while ($this->running) {
            $this->executeTransaction();
            
            echo "\n待機中...\n";
            sleep(2);
            pcntl_signal_dispatch();
        }
        
        echo "データベースワーカー終了\n";
    }
}
$db = new DatabaseTransaction();
$db->run();
?>
現在のシグナルマスクを取得
<?php
// 現在のシグナルマスクを取得
$current_mask = [];
pcntl_sigprocmask(SIG_SETMASK, [], $current_mask);
echo "現在ブロックされているシグナル:\n";
if (empty($current_mask)) {
    echo "  なし\n";
} else {
    foreach ($current_mask as $signal) {
        echo "  - シグナル {$signal}\n";
    }
}
// SIGINTとSIGTERMをブロック
echo "\nSIGINTとSIGTERMをブロックします\n";
pcntl_sigprocmask(SIG_BLOCK, [SIGINT, SIGTERM]);
// 再度確認
pcntl_sigprocmask(SIG_SETMASK, [], $current_mask);
echo "現在ブロックされているシグナル:\n";
foreach ($current_mask as $signal) {
    echo "  - シグナル {$signal}\n";
}
// 全てのブロックを解除
echo "\n全てのブロックを解除します\n";
pcntl_sigprocmask(SIG_SETMASK, []);
?>
SIG_BLOCKとSIG_UNBLOCKの違い
<?php
// 初期状態:何もブロックされていない
echo "=== SIG_BLOCK の例 ===\n";
// SIGINTをブロック
pcntl_sigprocmask(SIG_BLOCK, [SIGINT]);
echo "SIGINTをブロックしました\n";
// さらにSIGTERMをブロック(追加)
pcntl_sigprocmask(SIG_BLOCK, [SIGTERM]);
echo "SIGTERMもブロックしました\n";
// 現在のマスク:SIGINT + SIGTERM
pcntl_sigprocmask(SIG_SETMASK, [], $mask);
echo "ブロック中: " . count($mask) . "個のシグナル\n";
echo "\n=== SIG_UNBLOCK の例 ===\n";
// SIGINTのブロックを解除
pcntl_sigprocmask(SIG_UNBLOCK, [SIGINT]);
echo "SIGINTのブロックを解除しました\n";
// 現在のマスク:SIGTERMのみ
pcntl_sigprocmask(SIG_SETMASK, [], $mask);
echo "ブロック中: " . count($mask) . "個のシグナル\n";
echo "\n=== SIG_SETMASK の例 ===\n";
// マスクを完全に置き換え
pcntl_sigprocmask(SIG_SETMASK, [SIGHUP, SIGUSR1]);
echo "マスクをSIGHUPとSIGUSR1に設定しました\n";
pcntl_sigprocmask(SIG_SETMASK, [], $mask);
echo "ブロック中: " . count($mask) . "個のシグナル\n";
// 全てクリア
pcntl_sigprocmask(SIG_SETMASK, []);
echo "全てのブロックを解除しました\n";
?>
ファイル書き込みの保護
<?php
class SafeFileWriter {
    private $blocked_signals;
    
    public function writeFile($filename, $data) {
        echo "ファイル書き込み開始: {$filename}\n";
        
        // シグナルをブロック
        $signals_to_block = [SIGTERM, SIGINT, SIGHUP];
        pcntl_sigprocmask(SIG_BLOCK, $signals_to_block, $this->blocked_signals);
        
        try {
            // 一時ファイルに書き込み
            $temp_file = $filename . '.tmp';
            echo "  - 一時ファイルに書き込み中...\n";
            file_put_contents($temp_file, $data);
            sleep(1); // 書き込み処理をシミュレート
            
            // アトミックにリネーム
            echo "  - ファイルを移動中...\n";
            rename($temp_file, $filename);
            sleep(1);
            
            echo "ファイル書き込み完了\n";
            return true;
            
        } catch (Exception $e) {
            echo "エラー: " . $e->getMessage() . "\n";
            
            // 一時ファイルをクリーンアップ
            if (file_exists($temp_file)) {
                unlink($temp_file);
            }
            return false;
            
        } finally {
            // 必ずシグナルのブロックを解除
            pcntl_sigprocmask(SIG_SETMASK, $this->blocked_signals);
            pcntl_signal_dispatch();
            echo "シグナルブロック解除\n";
        }
    }
}
// シグナルハンドラ設定
$running = true;
pcntl_signal(SIGINT, function() use (&$running) {
    echo "\n終了シグナルを受信しました\n";
    $running = false;
});
$writer = new SafeFileWriter();
while ($running) {
    $writer->writeFile('important_data.txt', date('Y-m-d H:i:s') . "\n");
    
    echo "待機中... (Ctrl+Cで終了)\n";
    sleep(2);
    pcntl_signal_dispatch();
}
echo "プログラム終了\n";
?>
複数のクリティカルセクション
<?php
class CriticalSection {
    private static $depth = 0;
    private static $original_mask = [];
    
    public static function enter() {
        if (self::$depth === 0) {
            // 初回のみシグナルをブロック
            $signals = [SIGTERM, SIGINT, SIGHUP];
            pcntl_sigprocmask(SIG_BLOCK, $signals, self::$original_mask);
            echo str_repeat('  ', self::$depth) . "[ENTER] シグナルブロック開始\n";
        } else {
            echo str_repeat('  ', self::$depth) . "[ENTER] ネストされたセクション\n";
        }
        self::$depth++;
    }
    
    public static function leave() {
        self::$depth--;
        if (self::$depth === 0) {
            // 最後のleaveでシグナルを解除
            pcntl_sigprocmask(SIG_SETMASK, self::$original_mask);
            echo str_repeat('  ', self::$depth) . "[LEAVE] シグナルブロック解除\n";
            pcntl_signal_dispatch();
        } else {
            echo str_repeat('  ', self::$depth) . "[LEAVE] ネストから復帰\n";
        }
    }
}
function outer_function() {
    CriticalSection::enter();
    echo "外側の関数:処理1\n";
    sleep(1);
    
    inner_function();
    
    echo "外側の関数:処理2\n";
    sleep(1);
    CriticalSection::leave();
}
function inner_function() {
    CriticalSection::enter();
    echo "  内側の関数:処理\n";
    sleep(1);
    CriticalSection::leave();
}
// テスト実行
pcntl_signal(SIGINT, function() {
    echo "\nSIGINT受信!\n";
    exit(0);
});
echo "プログラム開始 (Ctrl+Cを試してください)\n\n";
outer_function();
echo "\nプログラム完了\n";
?>
デバッグ用:シグナルマスクの可視化
<?php
function print_signal_mask($label = "") {
    $current_mask = [];
    pcntl_sigprocmask(SIG_SETMASK, [], $current_mask);
    
    $signal_names = [
        SIGHUP  => 'SIGHUP',
        SIGINT  => 'SIGINT',
        SIGQUIT => 'SIGQUIT',
        SIGTERM => 'SIGTERM',
        SIGUSR1 => 'SIGUSR1',
        SIGUSR2 => 'SIGUSR2',
    ];
    
    echo "--- シグナルマスク" . ($label ? " ({$label})" : "") . " ---\n";
    if (empty($current_mask)) {
        echo "  ブロックなし\n";
    } else {
        foreach ($current_mask as $signal) {
            $name = $signal_names[$signal] ?? "SIGNAL_{$signal}";
            echo "  🚫 {$name} (ブロック中)\n";
        }
    }
    echo "\n";
}
// 初期状態
print_signal_mask("初期状態");
// SIGINTをブロック
pcntl_sigprocmask(SIG_BLOCK, [SIGINT]);
print_signal_mask("SIGINT追加後");
// SIGTERMも追加
pcntl_sigprocmask(SIG_BLOCK, [SIGTERM]);
print_signal_mask("SIGTERM追加後");
// SIGINTだけ解除
pcntl_sigprocmask(SIG_UNBLOCK, [SIGINT]);
print_signal_mask("SIGINT解除後");
// 全て解除
pcntl_sigprocmask(SIG_SETMASK, []);
print_signal_mask("全て解除後");
?>
重要なポイントと注意事項
⚠️ 必ずブロックを解除する
// ❌ 悪い例:例外でブロック解除が実行されない
pcntl_sigprocmask(SIG_BLOCK, [SIGINT]);
risky_operation(); // 例外が発生する可能性
pcntl_sigprocmask(SIG_UNBLOCK, [SIGINT]); // 実行されないかも
// ✅ 良い例:finallyで確実に解除
pcntl_sigprocmask(SIG_BLOCK, [SIGINT], $old_mask);
try {
    risky_operation();
} finally {
    pcntl_sigprocmask(SIG_SETMASK, $old_mask);
}
💡 ブロックできないシグナル
**SIGKILL(9)とSIGSTOP(19)**はブロックできません:
// これらは無視される
pcntl_sigprocmask(SIG_BLOCK, [SIGKILL, SIGSTOP]); // 効果なし
🔍 ブロックとハンドラの関係
シグナルをブロックしても、ハンドラの設定は有効です:
// ハンドラを設定
pcntl_signal(SIGINT, function() {
    echo "ハンドラ実行\n";
});
// SIGINTをブロック
pcntl_sigprocmask(SIG_BLOCK, [SIGINT]);
// Ctrl+Cを押す → 保留される
sleep(5);
// ブロック解除 → ハンドラが実行される
pcntl_sigprocmask(SIG_UNBLOCK, [SIGINT]);
pcntl_signal_dispatch(); // ここでハンドラが呼ばれる
ベストプラクティス
1. 必要最小限のブロック
// ✅ 良い例:必要な部分だけブロック
function critical_operation() {
    pcntl_sigprocmask(SIG_BLOCK, [SIGINT, SIGTERM], $old);
    try {
        // クリティカルな処理
    } finally {
        pcntl_sigprocmask(SIG_SETMASK, $old);
    }
}
// ❌ 悪い例:長時間ブロック
pcntl_sigprocmask(SIG_BLOCK, [SIGINT, SIGTERM]);
long_running_process(); // ユーザーが終了できない!
2. 旧マスクの保存
// ✅ 常に旧マスクを保存して復元
pcntl_sigprocmask(SIG_BLOCK, [SIGINT], $old_mask);
// 処理
pcntl_sigprocmask(SIG_SETMASK, $old_mask); // 元に戻す
3. デバッグログの追加
function block_signals($signals) {
    if (pcntl_sigprocmask(SIG_BLOCK, $signals, $old)) {
        error_log("Blocked signals: " . implode(',', $signals));
        return $old;
    }
    error_log("Failed to block signals");
    return null;
}
まとめ
pcntl_sigprocmask()は、PHPで高度なシグナル制御を実現するための重要な関数です。主なポイント:
- ✅ クリティカルセクションでシグナルを一時的にブロック
- ✅ データベーストランザクションやファイル操作を保護
- ✅ SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASKの使い分け
- ✅ 必ずfinallyブロックで解除する
- ✅ 旧マスクを保存して復元する
堅牢なプロセス制御が必要なシステムでは、この関数を活用して、中断されては困る処理を確実に保護しましょう!
関連記事
- pcntl_signal()でシグナルハンドラを設定
- pcntl_signal_dispatch()でシグナルを処理
- PHPでアトミックな操作を実装する方法
 
  
  
  
  