PHP 的 pcntl 擴展(需編譯時啟用 --enable-pcntl 及 posix)提供了進程創建、信號處理、程序替換等系統級能力。本文逐一講解常用函數並給出完整實戰範例。
declare(ticks=1) 實現異步信號處理。exit 結束。$status。options=WNOHANG 可非阻塞輪詢。PCNTL 使用 declare(ticks=N) 定義每執行 N 條底層語句檢查一次信號隊列。範例:每 5 秒觸發 SIGALRM 並打印:
declare(ticks = 1);
function signal_handler($signal) {
print "Caught SIGALRM\n";
pcntl_alarm(5);
}
pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);
for(;;) { } // 保持進程運行
pcntl_waitpid 返回的 $status 可配合以下函數分析:
pcntl_wifexited:是否正常退出pcntl_wexitstatus:取得退出碼pcntl_wifsignaled:是否因信號終止pcntl_wtermsig:終止信號編號以下類別封裝了進程池管理、任務分發、結果收集與僵屍進程清理:
class Signfork {
private $tmp_path = '/tmp/';
public function run($obj, $arg=1) {
if (!method_exists($obj, '__fork')) exit("Method '__fork' not found!");
// 解析參數並創建子進程
// ...
return $this->request($spawns);
}
private function spawn($obj, $i, $param=null) {
if (pcntl_fork() === 0) {
$cid = getmypid();
file_put_contents($this->tmp_path.'sfpid_'.$cid, $obj->__fork($param));
posix_kill($cid, SIGTERM);
exit;
}
}
private function request($spawns) {
$data = [];
for ($ids=0; $ids<$i; $ids++) {
while (!($cid=pcntl_waitpid(-1,$status,WNOHANG))) usleep(30000);
$tmpfile = $this->tmp_path.'sfpid_'.$cid;
$data[] = file_get_contents($tmpfile);
unlink($tmpfile);
}
return $data;
}
}
父進程 fork 子進程 → 子進程 fork 孫進程並退出 → 孫進程被 init 進程收養 → 孫進程結束由 init 自動回收。PHP 實現:
$pid = pcntl_fork();
if ($pid > 0) { sleep(5); exit(0); } // 父進程退出
else {
$gpid = pcntl_fork();
if ($gpid > 0) { pcntl_wait($status); exit(0); } // 子進程退出
else {
// 孫進程執行核心任務
posix_kill(getmypid(), SIGTERM);
exit(0);
}
}
pcntl_signal(SIGCHLD, SIG_IGN) 或雙重 fork 或定時 pcntl_waitpid(-1, $status, WNOHANG) 輪詢。getppid()==1 表示父進程已變為 init;或 kill($ppid, 0)==-1 檢測父進程是否存在。nohup php script.php & 或 screen 避免終端關閉導致進程終止。