## 孤兒進程
> 老爹死了你還沒死!
如果父進程先于子進程退出,則子進程成為孤兒進程,此時將自動被PID為1的進程(即init)接管。孤兒進程退出后,它的清理工作有祖先進程init自動處理。init清理沒那么及時,畢竟不是親爹。
>[warning] 子進程退出時,應當由父進程回收資源。應當避免父進程退出了,子進程還在的情況。__即: 應當避免孤兒進程。所以父進程要等子進程運行完了再退出。__
**參考代碼**
```
$pid = pcntl_fork();
//父進程和子進程都會執行下面代碼
if ($pid == -1) {
//錯誤處理:創建子進程失敗時返回-1
die('could not fork');
} else if ($pid) {
echo '父進程 id = ' . posix_getpid() . PHP_EOL;
exit(); // 父進程退出
} else {
//子進程得到的$pid為0, 所以這里是子進程執行的邏輯。
echo '子進程 id = ' . posix_getpid() . PHP_EOL;
while(true){
sleep(1); // 子進程一直運行
}
echo '子進程最后的父進程 id = ' . posix_getppid() . PHP_EOL;
}
```
**運行截圖**

## 僵尸進程
> 你死了沒人給你收尸!
如果子進程先退出,系統不會自動清理掉子進程的環境,而必須由父進程調用`wait`或`waitpid`函數來完成清理工作,如果父進程不做清理工作,則已經退出的子進程將成為僵尸進程(defunct),在系統中如果存在的僵尸(zombie)進程過多,將會影響系統的性能,所以必須對僵尸進程進行處理。
**參考案例**
```
// 創建一個子進程
$pid = pcntl_fork();
//父進程和子進程都會執行下面代碼
if ($pid == -1) {
//錯誤處理:創建子進程失敗時返回-1
die('could not fork');
} else if ($pid) {
echo '父進程 id = ' . posix_getpid() . PHP_EOL;
while(true){
sleep(1); // 但是父進程沒有回收
}
} else {
//子進程得到的$pid為0, 所以這里是子進程執行的邏輯。
echo '子進程 id = ' . posix_getpid() . PHP_EOL;
exit(); // 子進程已經退出
}
```
**截圖**

子進程變成僵尸進程標志性的`[ defunct ]`,還會看到一個新的列叫做`[ Z+ ]`,此處Z即為Zombie之意。
>[danger] ### 避免僵尸進程
在PHP中則是由`pcntl_wait()`和`pcntl_waitpid()`兩個函數來解決僵尸進程
### pcntl_wait() 函數
> pcntl_wait — 等待或返回 fork 的子進程狀態
>[warning] wait函數掛起當前進程的執行直到一個子進程退出或接收到一個信號要求中斷當前進程或調用一個信號處理函數。**如果一個子進程在調用此函數時已經退出(俗稱僵尸進程),此函數立刻返回。子進程使用的所有系統資源將被釋放。**
**參考代碼**
```
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// 父進程
echo '父進程 id = ' . posix_getpid() . PHP_EOL;
$i_ret = pcntl_wait($status); // 子進程退出立即執行。返回退出的子進程進程號,發生錯誤時返回 -1
echo $i_ret . ' : ' . $status . PHP_EOL;
// 保持父進程不退出
sleep(10);
} else {
// 子進程
for ($i = 1; $i <= 10; $i++) {
sleep(1);
echo "子進程 id = " . posix_getpid() . " 倒計時 : " . $i . PHP_EOL;
}
}
```
**執行截圖**

> `pcntl_wait() `函數在成功回收了子進程后,該函數當即會返回被回收子進程的PID
**語法詳解**
- 原型:`pcntl_wait ( int &$status [, int $options = 0 ] ) : int`
- 參數:$status這種用法叫做[ 值 - 參數 ],pcntl_wait()會將狀態信息存儲到這個變量中;$options是一個選項配置,如果貴系統支持wait3系統調用,這個參數就會生效,反之則傳進去也無法生效(使你我同僚倍感欣慰的是,絕大多數系統已經對wait3實現了支持甚至是wait4)。$option值則有`WNOHANG`或`WUNTRACED`二者可供選擇,而且也可以以二者進行或運算使得函數兼具兩種特性
- 返回:如尚未遇到任何錯誤,該函數返回被回收的子進程PID;如若出錯則會告知吾輩-1
>[warning] 默認情況下,以類pcntl_wait( $status )的方式發起調用則程式必為之所阻塞,一直到子進程結束該函數則會返回。
### WNOHANG
如果沒有子進程退出立刻返回,程序則不會被阻塞。
```
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// 父進程
echo '[x] 父進程 id = ' . posix_getpid() . PHP_EOL;
$i_ret = pcntl_wait($status, WNOHANG); // 使其實現非阻塞
echo '[x] 返回退出的子進程進程號 ' . $i_ret . ' : status = ' . $status . PHP_EOL;
// while保持父進程不退出
while (true) {
sleep(1);
}
} else {
// 子進程
for ($i = 1; $i <= 3; $i++) {
sleep(1);
echo "[x] 子進程 id = " . posix_getpid() . " 倒計時 : " . $i . PHP_EOL;
}
}
```

>[warning] 以上程序中,由于`pcntl_wait()`使用`WNOHANG`參數使其實現非阻塞,以至于子進程尚未結束生命周期而父進程便已然走完了`pcntl_wait()`流程而陷于其后的while()輪回之中。其結果推理便可自然可知,子進程必然無法逃脫淪為僵尸進程的厄運。
### WUNTRACED
子進程已經退出并且其狀態未報告時返回。
### 參數$status
與之配合的函數有如下列表:
- pcntl_wexitstatus:此函數可檢測進程退出時的錯誤碼,在*NIX里進程退出時默認錯誤碼是0,諸君亦可返其他任意數值,諸如exit( 250 ),此君可根據$status獲取子進程退出時的錯誤碼
- pcntl_wifexited:此君根據$status判斷子進程是否正常退出。APUE曾有記載進程完成自然生命周期亦或exit()均可視之為正常退出,被abort亦或終止于[ 信號 ](signal)
- pcntl_wifsignaled:此君較之前者,則用之于檢查子進程是否因信號而中斷
- pcntl_wifstopped:此君用于檢測子進程是否已停止(注意停止不是終止,諸君要理解為臨時掛起),然需使用了WUNTRACED作為$option的pcntl_waitpid()函數調用產生的status時才有效
- pcntl_wstopsig:此君則依賴前者,即僅在pcntl_wifstopped()返回 TRUE 時有效
- pcntl_wtermsig:此君依賴于pcntl_wifsignaled()為ture時檢測子進程因何種信號[ signal ]而終止
**參考代碼**
```
<?php
$pid = pcntl_fork();
if ($pid === 0) {
// 子進程
for ($i = 1; $i <= 3; $i++) {
sleep(1);
echo "[x] 子進程 id = " . posix_getpid() . " running " . $i . PHP_EOL;
}
exit(11);
}
// 父進程
echo '[x] 父進程 id = ' . posix_getpid() . PHP_EOL;
$i_ret = pcntl_wait($status, WUNTRACED);
echo $i_ret . " : " . $status . PHP_EOL;
$ret = pcntl_wexitstatus($status);
var_dump($ret);
$ret = pcntl_wifexited($status);
var_dump($ret);
$ret = pcntl_wifsignaled($status);
var_dump($ret);
$ret = pcntl_wifstopped($status);
var_dump($ret);
```

## pcntl_waitpid() 函數
> 掛起當前進程的執行直到參數pid指定的進程號的進程退出, 或接收到一個信號要求中斷當前進程或調用一個信號處理函數。
>[warning] 如果pid指定的子進程在此函數調用時已經退出(俗稱僵尸進程),此函數 將立刻返回。
**語法**
```
pcntl_waitpid ( int $pid , int &$status , int $options = 0 ) : int
```
**$pid 參數**
- `<-1`:等待任意進程組ID等于參數pid給定值的絕對值的進程
- `=-1`:等待任意子進程;與pcntl_wait函數行為一致
- `=0`:等待任意與調用進程組ID相同的子進程
- `>0`:等待進程號等于參數pid值的子進程
>[success] pcntl_waitpid()將會存儲狀態信息到status 參數上,這個通過status參數返回的狀態信息可以用以下函數 pcntl_wifexited(), pcntl_wifstopped(), pcntl_wifsignaled(), pcntl_wexitstatus(), pcntl_wtermsig()以及 pcntl_wstopsig()獲取其具體的值。
## pcntl_wifstopped()函數
> pcntl_wifstopped — 檢查子進程當前是否已經停止。
```
pcntl_wifstopped ( int $status ) : bool
```
>[warning] 僅查子進程當前是否停止; 此函數只有作用于使用了WUNTRACED作為 option的pcntl_waitpid()函數調用產生的status時才有效。
>[danger] **重要: 在此我需要向諸君說明一個進程的[ 終止 ]和[ 停止 ]是兩個決然不同的概念**
> - **[ 終止 ]意味著進程君生命周期已經完成,或正常完成或者異常終止;**
> - **[ 停止 ]意味著臨時掛起,還會復活繼續活動。**
>
> **在*NIX中,可以[ kill -STOP pid ]將指定pid的進程臨時掛起,此后便可使用pcntl_wifstopped()檢測其是否可以掛起停止,與之相反,便可用[ kill -CONT pid ]使之復活。**
**參考代碼**
```
<?php
$pid = pcntl_fork();
if ($pid === 0) {
// 子進程
for ($i = 1; $i <= 300; $i++) {
echo "[x] 子進程 id = " . posix_getpid() . " 心跳活著 " . $i . PHP_EOL;
sleep(1);
}
exit();
}
// 父進程
echo '[x] 父進程 id = ' . posix_getpid() . PHP_EOL;
while (true) {
$i_ret = pcntl_waitpid(0, $status, WNOHANG | WUNTRACED);
$isStop = pcntl_wifstopped($status); // 檢查子進程當前是否已經停止
echo "[x] 子進程當前是否已經停止: " . json_encode($isStop) . PHP_EOL;
sleep(1);
}
```

>[warning] 正如上圖所示,在`kill -STOP 22295`后,諸君可曾嘗試`kill -CONT 22295`?如有,可曾觀察程序" 是否停止:true "恢復為" 是否停止:false "?
> 答案是:事實上是沒有恢復
> 原因是:此處即為PHP文檔描述于進程控制粒度之粗狂,如諸君使用C語言便可使用使用WCONTINUED選項使進程文案恢復為" 是否停止:false "。然則,PHP文檔雖未標注,我們卻可通過如下方式使用該選項`$i_ret = pcntl_waitpid( 0, $status, WNOHANG | WUNTRACED | WCONTINUED )`
> *WCONTINUED選項相關資料可見于APUE 193頁(第三版)*
>[danger] `WNOHANG`,**此參使得pcntl_wait()亦或pcntl_waitpid()在尚未遇到任何子進程生命周期完成時馬上返回,而不會阻塞等待任一子進程結束。**
> 這一功能最大的作用就是:**我們期盼獲得到所有子進程的狀態而不是想被阻塞,這一要點在有多個子進程的時候顯得頗為至關重要。**
- 介紹
- 基礎
- Linux SOCKET編程詳解
- PHP SOCKET編程
- 1. socket 和 stream_socket
- 2. stream_socket_server 函數
- 3. stream_socket_accept 函數
- 什么是 EventLoop
- Linux 進程間通信
- 1.1 管道通信
- 系統調用
- IO多路復用
- epoll事件
- Redis IO多路復用
- select/poll/epoll介紹
- 函數接口
- pcntl 函數
- pcntl_wait 函數
- pcntl_alarm() 函數
- 高性能API社區閱讀筆記
- 子進程
- 進程回收
- 執行任務方式
- 進程監控monitor
- daemon進程
- Unix 信號
- 進程間通信
- libevent擴展
- Workerman專題
- 附錄一 調試工具
- B站Workerman服務器實戰原理解析筆記
- PHP實現一個webserver
- 其他