溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PHP中怎么是僵尸進(jìn)程

發(fā)布時(shí)間:2021-06-30 14:55:04 來源:億速云 閱讀:151 作者:Leah 欄目:編程語言

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)PHP中怎么是僵尸進(jìn)程,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

基本概念

我們知道在unix/linux中,正常情況下,子進(jìn)程是通過父進(jìn)程創(chuàng)建的,子進(jìn)程在創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個(gè)異步過程,即父進(jìn)程永遠(yuǎn)無法預(yù)測子進(jìn)程 到底什么時(shí)候結(jié)束。當(dāng)一個(gè) 進(jìn)程完成它的工作終止之后,它的父進(jìn)程需要調(diào)用wait()或者waitpid()系統(tǒng)調(diào)用取得子進(jìn)程的終止?fàn)顟B(tài)。

孤兒進(jìn)程

一個(gè)父進(jìn)程退出,而它的一個(gè)或多個(gè)子進(jìn)程還在運(yùn)行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號(hào)為1)所收養(yǎng),并由init進(jìn)程對(duì)它們完成狀態(tài)收集工作。

僵尸進(jìn)程

一個(gè)進(jìn)程使用fork創(chuàng)建子進(jìn)程,如果子進(jìn)程退出,而父進(jìn)程并沒有調(diào)用wait或waitpid獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的進(jìn)程描述符仍然保存在系統(tǒng)中。這種進(jìn)程稱之為僵死進(jìn)程。

問題及危害

unix提供了一種機(jī)制可以保證只要父進(jìn)程想知道子進(jìn)程結(jié)束時(shí)的狀態(tài)信息, 就可以得到。這種機(jī)制就是: 在每個(gè)進(jìn)程退出的時(shí)候,內(nèi)核釋放該進(jìn)程所有的資源,包括打開的文件,占用的內(nèi)存等。但是仍然為其保留一定的信息(包括進(jìn)程號(hào)the process ID,退出狀態(tài)the termination status of the process,運(yùn)行時(shí)間the amount of CPU time taken by the process等)。直到父進(jìn)程通過wait / waitpid來取時(shí)才釋放。

但這樣就導(dǎo)致了問題,如果進(jìn)程不調(diào)用wait / waitpid的話, 那么保留的那段信息就不會(huì)釋放,其進(jìn)程號(hào)就會(huì)一直被占用,但是系統(tǒng)所能使用的進(jìn)程號(hào)是有限的,如果大量的產(chǎn)生僵死進(jìn)程,將因?yàn)闆]有可用的進(jìn)程號(hào)而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程. 此即為僵尸進(jìn)程的危害,應(yīng)當(dāng)避免。

孤兒進(jìn)程是沒有父進(jìn)程的進(jìn)程,孤兒進(jìn)程這個(gè)重任就落到了init進(jìn)程身上,init進(jìn)程就好像是一個(gè)民政局,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作。每當(dāng)出現(xiàn)一個(gè)孤兒進(jìn)程的時(shí)候,內(nèi)核就把孤 兒進(jìn)程的父進(jìn)程設(shè)置為init,而init進(jìn)程會(huì)循環(huán)地wait()它的已經(jīng)退出的子進(jìn)程。

這樣,當(dāng)一個(gè)孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時(shí)候,init進(jìn)程就會(huì)代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會(huì)有什么危害。任何一個(gè)子進(jìn)程(init除外)在exit()之后,并非馬上就消失掉,而是留下一個(gè)稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu),等待父進(jìn)程處理。這是每個(gè) 子進(jìn)程在結(jié)束時(shí)都要經(jīng)過的階段。

如果子進(jìn)程在exit()之后,父進(jìn)程沒有來得及處理,這時(shí)用ps命令就能看到子進(jìn)程的狀態(tài)是“Z”。如果父進(jìn)程能及時(shí) 處理,可能用ps命令就來不及看到子進(jìn)程的僵尸狀態(tài),但這并不等于子進(jìn)程不經(jīng)過僵尸狀態(tài)。如果父進(jìn)程在子進(jìn)程結(jié)束之前退出,則子進(jìn)程將由init接管。init將會(huì)以父進(jìn)程的身份對(duì)僵尸狀態(tài)的子進(jìn)程進(jìn)行處理。

僵尸進(jìn)程危害場景

例如有個(gè)進(jìn)程,它定期的產(chǎn) 生一個(gè)子進(jìn)程,這個(gè)子進(jìn)程需要做的事情很少,做完它該做的事情之后就退出了,因此這個(gè)子進(jìn)程的生命周期很短,但是,父進(jìn)程只管生成新的子進(jìn)程,至于子進(jìn)程 退出之后的事情,則一概不聞不問,這樣,系統(tǒng)運(yùn)行上一段時(shí)間之后,系統(tǒng)中就會(huì)存在很多的僵死進(jìn)程,倘若用ps命令查看的話,就會(huì)看到很多狀態(tài)為Z的進(jìn)程。

嚴(yán)格地來說,僵死進(jìn)程并不是問題的根源,罪魁禍?zhǔn)资钱a(chǎn)生出大量僵死進(jìn)程的那個(gè)父進(jìn)程。因此,當(dāng)我們尋求如何消滅系統(tǒng)中大量的僵死進(jìn)程時(shí),答案就是把產(chǎn)生大 量僵死進(jìn)程的那個(gè)元兇槍斃掉(也就是通過kill發(fā)送SIGTERM或者SIGKILL信號(hào)啦)。

槍斃了元兇進(jìn)程之后,它產(chǎn)生的僵死進(jìn)程就變成了孤兒進(jìn) 程,這些孤兒進(jìn)程會(huì)被init進(jìn)程接管,init進(jìn)程會(huì)wait()這些孤兒進(jìn)程,釋放它們占用的系統(tǒng)進(jìn)程表中的資源,這樣,這些已經(jīng)僵死的孤兒進(jìn)程 就能瞑目而去了。

孤兒進(jìn)程和僵尸進(jìn)程測試

1、孤兒進(jìn)程被init進(jìn)程收養(yǎng)

$pid = pcntl_fork();

if ($pid > 0) {

  // 顯示父進(jìn)程的進(jìn)程ID,這個(gè)函數(shù)可以是getmypid(),也可以用posix_getpid()

  echo "Father PID:" . getmypid() . PHP_EOL;

  // 讓父進(jìn)程停止兩秒鐘,在這兩秒內(nèi),子進(jìn)程的父進(jìn)程ID還是這個(gè)父進(jìn)程

  sleep(2);

} else if (0 == $pid) {

  // 讓子進(jìn)程循環(huán)10次,每次睡眠1s,然后每秒鐘獲取一次子進(jìn)程的父進(jìn)程進(jìn)程ID

  for ($i = 1; $i <= 10; $i++) {

    sleep(1);

    // posix_getppid()函數(shù)的作用就是獲取當(dāng)前進(jìn)程的父進(jìn)程進(jìn)程ID

    echo posix_getppid() . PHP_EOL;

  }

} else {

  echo "fork error." . PHP_EOL;

}

測試結(jié)果:

php daemo001.php

Father PID:18046

18046

18046

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ 1

1

1

1

1

1

1

1

2、僵尸進(jìn)程和危害 

執(zhí)行以下代碼 php zombie1.php

$pid = pcntl_fork();

if( $pid > 0 ){

  // 下面這個(gè)函數(shù)可以更改php進(jìn)程的名稱

  cli_set_process_title('php father process');

  // 讓主進(jìn)程休息60秒鐘

  sleep(60);

} else if( 0 == $pid ) {

  cli_set_process_title('php child process');

  // 讓子進(jìn)程休息10秒鐘,但是進(jìn)程結(jié)束后,父進(jìn)程不對(duì)子進(jìn)程做任何處理工作,這樣這個(gè)子進(jìn)程就會(huì)變成僵尸進(jìn)程

  sleep(10);

} else {

  exit('fork error.'.PHP_EOL);

}

執(zhí)行結(jié)果,另外一個(gè)終端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18458 0.5 1.2 204068 25920 pts/1  S+  16:34  0:00 php father process

www   18459 0.0 0.3 204068 6656 pts/1  S+  16:34  0:00 php child process

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18458 0.0 1.2 204068 25920 pts/1  S+  16:34  0:00 php father process

www   18459 0.0 0.0   0   0 pts/1  Z+  16:34  0:00 [php] <defunct>

通過執(zhí)行 ps -aux 命令可以看到,當(dāng)程序在前十秒內(nèi)運(yùn)行的時(shí)候,php child process 的狀態(tài)列為 [S+],然而在十秒鐘過后,這個(gè)狀態(tài)變成了 [Z+],也就是變成了危害系統(tǒng)的僵尸進(jìn)程。

那么,問題來了?如何避免僵尸進(jìn)程呢?

PHP通過 pcntl_wait() 和 pcntl_waitpid() 兩個(gè)函數(shù)來幫我們解決這個(gè)問題。了解Linux系統(tǒng)編程的應(yīng)該知道,看名字就知道這其實(shí)就是PHP把C語言中的 wait() 和 waitpid() 包裝了一下。

通過代碼演示 pcntl_wait() 來避免僵尸進(jìn)程。

pcntl_wait() 函數(shù):

這個(gè)函數(shù)的作用就是 “ 等待或者返回子進(jìn)程的狀態(tài) ”,當(dāng)父進(jìn)程執(zhí)行了該函數(shù)后,就會(huì)阻塞掛起等待子進(jìn)程的狀態(tài)一直等到子進(jìn)程已經(jīng)由于某種原因退出或者終止。

換句話說就是如果子進(jìn)程還沒結(jié)束,那么父進(jìn)程就會(huì)一直等等等,如果子進(jìn)程已經(jīng)結(jié)束,那么父進(jìn)程就會(huì)立刻得到子進(jìn)程狀態(tài)。這個(gè)函數(shù)返回退出的子進(jìn)程的進(jìn)程 ID 或者失敗返回 -1。

執(zhí)行以下代碼 zombie2.php

$pid = pcntl_fork();

if ($pid > 0) {

  // 下面這個(gè)函數(shù)可以更改php進(jìn)程的名稱

  cli_set_process_title('php father process');

  // 返回$wait_result,就是子進(jìn)程的進(jìn)程號(hào),如果子進(jìn)程已經(jīng)是僵尸進(jìn)程則為0

  // 子進(jìn)程狀態(tài)則保存在了$status參數(shù)中,可以通過pcntl_wexitstatus()等一系列函數(shù)來查看$status的狀態(tài)信息是什么

  $wait_result = pcntl_wait($status);

  print_r($wait_result);

  print_r($status);

  // 讓主進(jìn)程休息60秒鐘

  sleep(60);

} else if (0 == $pid) {

  cli_set_process_title('php child process');

  // 讓子進(jìn)程休息10秒鐘,但是進(jìn)程結(jié)束后,父進(jìn)程不對(duì)子進(jìn)程做任何處理工作,這樣這個(gè)子進(jìn)程就會(huì)變成僵尸進(jìn)程

  sleep(10);

} else {

  exit('fork error.' . PHP_EOL);

}

在另外一個(gè)終端中通過ps -aux查看,可以看到在前十秒內(nèi),php child process 是 [S+] 狀態(tài),然后十秒鐘過后進(jìn)程消失了,也就是被父進(jìn)程回收了,沒有變成僵尸進(jìn)程。

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18519 0.5 1.2 204068 25576 pts/1  S+  16:42  0:00 php father process

www   18520 0.0 0.3 204068 6652 pts/1  S+  16:42  0:00 php child process

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18519 0.0 1.2 204068 25576 pts/1  S+  16:42  0:00 php father process

但是,pcntl_wait() 有個(gè)很大的問題,就是阻塞。父進(jìn)程只能掛起等待子進(jìn)程結(jié)束或終止,在此期間父進(jìn)程什么都不能做,這并不符合多快好省原則,所以 pcntl_waitpid() 閃亮登場。pcntl_waitpid( pid, &status, $option = 0 )的第三個(gè)參數(shù)如果設(shè)置為WNOHANG,那么父進(jìn)程不會(huì)阻塞一直等待到有子進(jìn)程退出或終止,否則將會(huì)和pcntl_wait()的表現(xiàn)類似。

修改第三個(gè)案例的代碼,但是,我們并不添加WNOHANG,演示說明pcntl_waitpid()功能:

$pid = pcntl_fork();

if ($pid > 0) {

  // 下面這個(gè)函數(shù)可以更改php進(jìn)程的名稱

  cli_set_process_title('php father process');

  // 返回值保存在$wait_result中

  // $pid參數(shù)表示 子進(jìn)程的進(jìn)程ID

  // 子進(jìn)程狀態(tài)則保存在了參數(shù)$status中

  // 將第三個(gè)option參數(shù)設(shè)置為常量WNOHANG,則可以避免主進(jìn)程阻塞掛起,此處父進(jìn)程將立即返回繼續(xù)往下執(zhí)行剩下的代碼

  $wait_result = pcntl_waitpid($pid, $status);

  var_dump($wait_result);

  var_dump($status);

  // 讓主進(jìn)程休息60秒鐘

  sleep(60);

} else if (0 == $pid) {

  cli_set_process_title('php child process');

  // 讓子進(jìn)程休息10秒鐘,但是進(jìn)程結(jié)束后,父進(jìn)程不對(duì)子進(jìn)程做任何處理工作,這樣這個(gè)子進(jìn)程就會(huì)變成僵尸進(jìn)程

  sleep(10);

} else {

  exit('fork error.' . PHP_EOL);

}

下面是運(yùn)行結(jié)果,一個(gè)執(zhí)行php zombie3.php 程序的終端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ php zombie3.php

int(18586)

int(0)

^C  

ctrl-c 發(fā)送 SIGINT 信號(hào)給前臺(tái)進(jìn)程組中的所有進(jìn)程。常用于終止正在運(yùn)行的程序。

下面是ps -aux終端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18605 0.3 1.2 204068 25756 pts/1  S+  16:52  0:00 php father process

www   18606 0.0 0.3 204068 6636 pts/1  S+  16:52  0:00 php child process

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18605 0.1 1.2 204068 25756 pts/1  S+  16:52  0:00 php father process

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18605 0.0 1.2 204068 25756 pts/1  S+  16:52  0:00 php father process

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php // ctrl-c 后不再被阻塞

www@iZ2zec3dge6rwz2uw4tveuZ:~$

實(shí)際上可以看到主進(jìn)程是被阻塞的,一直到第十秒子進(jìn)程退出了,父進(jìn)程不再阻塞  

修改第四段代碼,添加第三個(gè)參數(shù)WNOHANG,代碼如下:

$pid = pcntl_fork();

if ($pid > 0) {

  // 下面這個(gè)函數(shù)可以更改php進(jìn)程的名稱

  cli_set_process_title('php father process');

  // 返回值保存在$wait_result中

  // $pid參數(shù)表示 子進(jìn)程的進(jìn)程ID

  // 子進(jìn)程狀態(tài)則保存在了參數(shù)$status中

  // 將第三個(gè)option參數(shù)設(shè)置為常量WNOHANG,則可以避免主進(jìn)程阻塞掛起,此處父進(jìn)程將立即返回繼續(xù)往下執(zhí)行剩下的代碼

  $wait_result = pcntl_waitpid($pid, $status, WNOHANG);

  var_dump($wait_result);

  var_dump($status);

  echo "不阻塞,運(yùn)行到這里" . PHP_EOL;

  // 讓主進(jìn)程休息60秒鐘

  sleep(60);

} else if (0 == $pid) {

  cli_set_process_title('php child process');

  // 讓子進(jìn)程休息10秒鐘,但是進(jìn)程結(jié)束后,父進(jìn)程不對(duì)子進(jìn)程做任何處理工作,這樣這個(gè)子進(jìn)程就會(huì)變成僵尸進(jìn)程

  sleep(10);

} else {

  exit('fork error.' . PHP_EOL);

}

執(zhí)行 php zombie4.php

www@iZ2zec3dge6rwz2uw4tveuZ:~/test$ php zombie4.php

int(0)

int(0)

不阻塞,運(yùn)行到這里 

另一個(gè)ps -aux終端窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18672 0.3 1.2 204068 26284 pts/1  S+  17:00  0:00 php father process

www   18673 0.0 0.3 204068 6656 pts/1  S+  17:00  0:00 php child process

www@iZ2zec3dge6rwz2uw4tveuZ:~$ ps -aux|grep -v "grep\|nginx\|php-fpm" | grep php

www   18672 0.0 1.2 204068 26284 pts/1  S+  17:00  0:00 php father process

www   18673 0.0 0.0   0   0 pts/1  Z+  17:00  0:00 [php] <defunct>

實(shí)際上可以看到主進(jìn)程是被阻塞的,一直到第十秒子進(jìn)程退出了,父進(jìn)程不再阻塞。  

問題出現(xiàn)了,竟然php child process進(jìn)程狀態(tài)竟然變成了[Z+],這是怎么搞得?回頭分析一下代碼:
我們看到子進(jìn)程是睡眠了十秒鐘,而父進(jìn)程在執(zhí)行pcntl_waitpid()之前沒有任何睡眠且本身不再阻塞,所以,主進(jìn)程自己先執(zhí)行下去了,而子進(jìn)程在足足十秒鐘后才結(jié)束,進(jìn)程狀態(tài)自然無法得到回收。

如果我們將代碼修改一下,就是在主進(jìn)程的pcntl_waitpid()前睡眠15秒鐘,這樣就可以回收子進(jìn)程了。但是即便這樣修改,細(xì)心想的話還是會(huì)有個(gè)問題,那就是在子進(jìn)程結(jié)束后,在父進(jìn)程執(zhí)行pcntl_waitpid()回收前,有五秒鐘的時(shí)間差,在這個(gè)時(shí)間差內(nèi),php child process也將會(huì)是僵尸進(jìn)程。

上述就是小編為大家分享的PHP中怎么是僵尸進(jìn)程了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI