java 僵尸进程_孤儿进程与僵尸进程
開(kāi)發(fā)中,在io密集型的場(chǎng)景下,我們可以使用多進(jìn)程(多線程/協(xié)成更nber)來(lái)提高任務(wù)的處理速度。這就需要主進(jìn)程需要等待所有工作進(jìn)程執(zhí)行完畢后才可以去匯總結(jié)果后退出。
但如果不規(guī)范的編寫程序,就可能導(dǎo)致系統(tǒng)產(chǎn)生孤兒進(jìn)程/僵尸進(jìn)程。
父/子進(jìn)程執(zhí)行的流程
孤兒進(jìn)程/僵尸進(jìn)程
1、孤兒進(jìn)程:子進(jìn)程執(zhí)行完畢時(shí)發(fā)現(xiàn)父進(jìn)程已退出,子進(jìn)程變成為了孤兒進(jìn)程。孤兒進(jìn)程后期會(huì)被系統(tǒng)的 init 進(jìn)程接管,并 wait/waitpid 其執(zhí)行狀態(tài)做回收處理。對(duì)系統(tǒng)并無(wú)危害。
2、僵尸進(jìn)程:子進(jìn)程執(zhí)行完畢時(shí)發(fā)現(xiàn)父進(jìn)程未退出,會(huì)向父進(jìn)程發(fā)送 SIGCHLD 信號(hào)。但父進(jìn)程沒(méi)有使用 wait/waitpid 或其他方式處理 SIGCHLD 信號(hào)來(lái)回收子進(jìn)程,子進(jìn)程變成為了對(duì)系統(tǒng)有害的僵尸進(jìn)程。
僵尸進(jìn)程無(wú)法被系統(tǒng)有效的回收,ps 查看時(shí)狀態(tài)為 Z 的即為僵尸進(jìn)程,或 top 命令可直接看到 zombie 的個(gè)數(shù)。僵尸進(jìn)程的父進(jìn)程此時(shí)一定仍在運(yùn)行,產(chǎn)生僵尸進(jìn)程的元兇其實(shí)是他們的父進(jìn)程,殺掉父進(jìn)程,僵尸進(jìn)程就變?yōu)榱斯聝哼M(jìn)程,便可以轉(zhuǎn)交給 init 進(jìn)程回收處理。
多進(jìn)程編程
在多進(jìn)程開(kāi)發(fā)中,我們無(wú)法統(tǒng)一確信父進(jìn)程會(huì)先于任何子進(jìn)程退出,或者不少場(chǎng)景父進(jìn)程在創(chuàng)建子進(jìn)程后并不會(huì)退出。這就使得我們要在父進(jìn)程中處理子進(jìn)程的 SIGCHLD 信號(hào),否則就有可能產(chǎn)生僵尸進(jìn)程。
用 PHP 來(lái)實(shí)現(xiàn)一個(gè)較為標(biāo)準(zhǔn)的多進(jìn)程模型。
/**
* 安全的多進(jìn)程處理
*/
if (!function_exists('pcntl_fork')) {
trigger_error("need pcntl extension!", E_USER_ERROR);
}
const WORKER_NUM = 4;
$workers_pid = [];
for ($i = 0; $i < WORKER_NUM; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
trigger_error("child process create failed!");
}
if ($pid == 0) {
// 子進(jìn)程執(zhí)行模塊 編寫子進(jìn)程的執(zhí)行邏輯
echo "I am child pid: " . getmypid() . PHP_EOL;
sleep(rand(1, 3));
exit(0);
} else {
// 父進(jìn)程收集子進(jìn)程的pid
$workers_pid[] = $pid;
}
}
// 父進(jìn)程需使用 wait/waitpid 處理子進(jìn)程的 SIGCHLD 信號(hào) 防止子進(jìn)程成為僵尸進(jìn)程
// 同時(shí)也可以同步父子進(jìn)程的執(zhí)行順序 等待子進(jìn)程全部執(zhí)行完成 防止成為孤兒進(jìn)程
while (true) {
// 若仍有未退出的子進(jìn)程
if (!empty($workers_pid)) {
// pcntl_wait 會(huì)阻塞并等待子進(jìn)程發(fā)送的信號(hào)量
$worker_pid = pcntl_wait(&$status);
if ($status == 0) {
echo 'child ' . $worker_pid . ' safe exited!' . PHP_EOL;
} else {
echo 'child ' . $worker_pid . ' wrong end with status: ' . $status . PHP_EOL;
}
// 刪除子進(jìn)程的 pid
$key = array_search($worker_pid, $workers_pid);
unset($workers_pid[$key]);
} else {
// 所有子進(jìn)程都已執(zhí)行完畢
break;
}
}
// 如果我們沒(méi)有使用 wait/waitpid 處理子進(jìn)程的 SIGCHLD 信號(hào)
// 且在子進(jìn)程結(jié)束時(shí)父進(jìn)程還未退出 則會(huì)產(chǎn)生僵尸進(jìn)程占用系統(tǒng)資源
echo "main end" . PHP_EOL;
wait/waitpid 函數(shù)會(huì)阻塞父進(jìn)程等待子進(jìn)程發(fā)送 SIGCHLD 信號(hào),父進(jìn)程處理并回收對(duì)應(yīng)的子進(jìn)程,當(dāng)所有的子進(jìn)程回收完畢后,即 workers_pid 數(shù)組為空時(shí),主進(jìn)程正常退出即可。
形象的理解下:
父進(jìn)程作為子進(jìn)程的監(jiān)護(hù)人,在子進(jìn)程運(yùn)行結(jié)束后負(fù)責(zé)清理和回收相關(guān)資源是理所當(dāng)然的。
子進(jìn)程在運(yùn)行結(jié)束時(shí)會(huì)告訴父進(jìn)程,我運(yùn)行完了,把我回收掉吧,騰出地兒來(lái)。
父進(jìn)程可以通過(guò) await/awaitpid 收到子進(jìn)程運(yùn)行結(jié)束的信號(hào)?SIGCHLD,并回收子進(jìn)程。
可有些父進(jìn)程不負(fù)責(zé)任,丟下子進(jìn)程直接跑掉了,子進(jìn)程便成了孤兒進(jìn)程,這時(shí)福利院長(zhǎng) init 便過(guò)來(lái)接管收留了這些子進(jìn)程,讓它們成為了自己的孩子,耐心的傾聽(tīng)它們何時(shí)執(zhí)行完畢,把它們回收。
有些父進(jìn)程更過(guò)分,雖然沒(méi)跑路,但堵住了自己負(fù)責(zé)監(jiān)聽(tīng) SIGCHLD 信號(hào)的耳朵,子進(jìn)程根本沒(méi)辦法喊應(yīng)它,就變成了僵尸進(jìn)程。init 看到父進(jìn)程還在那,自己也不能過(guò)去接管。如果父進(jìn)程一會(huì)兒突然跑掉了,那 init 可以過(guò)去接管這些子進(jìn)程,因?yàn)榇藭r(shí)這些子進(jìn)程已經(jīng)沒(méi)有監(jiān)護(hù)人,他們是孤兒進(jìn)程了,init 可以接管它們。但如果父進(jìn)程一直不走,那這些僵尸子進(jìn)程就會(huì)一直在那里呆著占用著系統(tǒng)資源。
補(bǔ)充
1、父進(jìn)程退出時(shí)子進(jìn)程仍在運(yùn)行,則會(huì)使得子進(jìn)程變?yōu)楣聝哼M(jìn)程,系統(tǒng)的 pid 為 1 的 init 進(jìn)程 將會(huì)接管這些孤兒進(jìn)程,待其運(yùn)行結(jié)束后回收資源。
2、子進(jìn)程退出時(shí)父進(jìn)程仍在運(yùn)行,且父進(jìn)程沒(méi)有對(duì)子進(jìn)程發(fā)送的?SIGCHLD 信號(hào)進(jìn)行處理(通常我們是調(diào)用 wait/waitpid 進(jìn)行處理的),則會(huì)使得子進(jìn)程成為僵尸進(jìn)程 zombie,僵尸進(jìn)程會(huì)繼續(xù)占用系統(tǒng)資源。若父進(jìn)程稍后退出,則僵尸進(jìn)程會(huì)轉(zhuǎn)為孤兒進(jìn)程,進(jìn)入 1 的處理流程。若父進(jìn)程處于長(zhǎng)期運(yùn)行狀態(tài),則這些占用系統(tǒng)資源的僵尸進(jìn)程會(huì)降低系統(tǒng)性能。
總結(jié)
以上是生活随笔為你收集整理的java 僵尸进程_孤儿进程与僵尸进程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: java 第三方代码_Java:如何使用
- 下一篇: java面板如何设置大小_java面板调
