首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 實現守護進程:從 nohup 到 pcntl_fork 雙重 Fork 完整指南

     編輯:PHP 綜合知識

什麼是守護進程

守護進程在背景獨立於終端運行,不受使用者登出、終端關閉影響。典型特徵:無控制終端、在根目錄下運行、關閉標準輸入輸出、忽略終端信號。

方法一:系統工具 nohup(最簡單)

# 背景執行,輸出導向 log.txt,忽略 SIGHUP
nohup php worker.php > log.txt 2>&1 &

# 查看進程
ps aux | grep worker.php

# 停止
kill 

優點:零代碼修改。缺點:無法在 PHP 內部控制、難以管理多進程、標準輸出需手動導向。

方法二:PHP pcntl 雙重 Fork(標準 Unix 守護進程模式)

<?php
declare(ticks=1); // 讓信號處理器能在非阻塞操作中被調用

function daemonize(bool $redirectStd = true): void
{
    // 第一次 fork:父進程退出,子進程成為孤兒進程,由 init(1) 收養
    $pid = pcntl_fork();
    if ($pid === -1) {
        throw new RuntimeException('First fork failed');
    } elseif ($pid > 0) {
        exit(0); // 父進程退出,讓 shell 以為命令已結束
    }

    // 脫離終端:建立新 session,成為 session leader,失去控制終端
    if (posix_setsid() === -1) {
        throw new RuntimeException('posix_setsid failed');
    }

    // 第二次 fork:確保進程不是 session leader,無法重新獲得控制終端
    $pid = pcntl_fork();
    if ($pid === -1) {
        throw new RuntimeException('Second fork failed');
    } elseif ($pid > 0) {
        exit(0); // 第一子進程退出,最終守護進程為第二子進程
    }

    // 可選:切換工作目錄到根目錄,避免掛載點無法卸載
    chdir('/');

    // 可選:重設 umask,確保建立檔案權限可控
    umask(0);

    // 可選:重導向標準輸入/輸出/錯誤到 /dev/null 或日誌檔
    if ($redirectStd) {
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);
        $null = fopen('/dev/null', 'r');
        fopen('/dev/null', 'w'); // stdout
        fopen('/dev/null', 'w'); // stderr
        // 或導向日誌檔:
        // $log = fopen('/var/log/mydaemon.log', 'a');
        // $logErr = fopen('/var/log/mydaemon_error.log', 'a');
    }
}

// 使用範例
daemonize();

// 註冊信號處理器
pcntl_signal(SIGTERM, function () {
    // 優雅關閉:釋放資源、存檔狀態
    syslog(LOG_INFO, 'Daemon received SIGTERM, shutting down...');
    exit(0);
});

// 主迴圈
while (true) {
    // 你的業務邏輯:監聽隊列、定時任務、Socket 服務等
    doWork();
    sleep(1);
}

function doWork(): void
{
    // 實際工作內容
}

進階:Systemd 服務單元管理(生產環境推薦)

# /etc/systemd/system/php-worker.service
[Unit]
Description=PHP Background Worker
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php /var/www/app/worker.php
Restart=always
RestartSec=5
StandardOutput=append:/var/log/php-worker.log
StandardError=append:/var/log/php-worker_error.log

[Install]
WantedBy=multi-user.target

啟用:systemctl enable --now php-worker。優點:開機自啟、自動重啟、日誌集中、資源限制(MemoryLimit、CPUQuota)。

常見坑與解決

問題原因解決
pcntl_fork 未定義未安裝 pcntl 擴展或 Windows 環境Linux 安裝 php-pcntl;Windows 改用 nohup/Systemd/COM
進程變殭屍進程父進程未 wait 子進程註冊 SIGCHLD handler 呼叫 pcntl_waitpid(-1, $status, WNOHANG)
檔案描述符洩漏Fork 後子進程繼承父進程開啟的連線Fork 後在子進程重新建立 DB/Redis 連線
標準輸出卡住管道滿未讀取重導向到檔案或 /dev/null,或使用非阻塞 I/O
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved