天天看點

Python--詳細講解僵屍程序與孤兒程序

一:僵屍程序(有害)
  僵屍程序:一個程序使用fork建立子程序,如果子程序退出,而父程序并沒有調用wait或waitpid擷取子程序的狀态資訊,那麼子程序的程序描述符仍然儲存在系統中。這種程序稱之為僵死程序。詳解如下

我們知道在unix/linux中,正常情況下子程序是通過父程序建立的,子程序在建立新的程序。子程序的結束和父程序的運作是一個異步過程,即父程序永遠無法預測子程序到底什麼時候結束,如果子程序一結束就立刻回收其全部資源,那麼在父程序内将無法擷取子程序的狀态資訊。

是以,UNⅨ提供了一種機制可以保證父程序可以在任意時刻擷取子程序結束時的狀态資訊:
1、在每個程序退出的時候,核心釋放該程序所有的資源,包括打開的檔案,占用的記憶體等。但是仍然為其保留一定的資訊(包括程序号the process ID,退出狀态the termination status of the process,運作時間the amount of CPU time taken by the process等)
2、直到父程序通過wait / waitpid來取時才釋放. 但這樣就導緻了問題,如果程序不調用wait / waitpid的話,那麼保留的那段資訊就不會釋放,其程序号就會一直被占用,但是系統所能使用的程序号是有限的,如果大量的産生僵死程序,将因為沒有可用的程序号而導緻系統不能産生新的程序. 此即為僵屍程序的危害,應當避免。

  任何一個子程序(init除外)在exit()之後,并非馬上就消失掉,而是留下一個稱為僵屍程序(Zombie)的資料結構,等待父程序處理。這是每個子程序在結束時都要經過的階段。如果子程序在exit()之後,父程序沒有來得及處理,這時用ps指令就能看到子程序的狀态是“Z”。如果父程序能及時 處理,可能用ps指令就來不及看到子程序的僵屍狀态,但這并不等于子程序不經過僵屍狀态。  如果父程序在子程序結束之前退出,則子程序将由init接管。init将會以父程序的身份對僵屍狀态的子程序進行處理。

二:孤兒程序(無害)

  孤兒程序:一個父程序退出,而它的一個或多個子程序還在運作,那麼那些子程序将成為孤兒程序。孤兒程序将被init程序(程序号為1)所收養,并由init程序對它們完成狀态收集工作。

  孤兒程序是沒有父程序的程序,孤兒程序這個重任就落到了init程序身上,init程序就好像是一個民政局,專門負責處理孤兒程序的善後工作。每當出現一個孤兒程序的時候,核心就把孤 兒程序的父程序設定為init,而init程序會循環地wait()它的已經退出的子程序。這樣,當一個孤兒程序凄涼地結束了其生命周期的時候,init程序就會代表黨和政府出面處理它的一切善後工作。是以孤兒程序并不會有什麼危害。

我們來測試一下(建立完子程序後,主程序所在的這個腳本就退出了,當父程序先于子程序結束時,子程序會被init收養,成為孤兒程序,而非僵屍程序),檔案内容
           
import os
import sys
import time

pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
#執行pid=os.fork()則會生成一個子程序
#傳回值pid有兩種值:
#    如果傳回的pid值為0,表示在子程序當中
#    如果傳回的pid值>0,表示在父程序當中
if pid > 0:
    print 'father died..'
    sys.exit(0)

# 保證主線程退出完畢
time.sleep(1)
print 'im child', os.getpid(), os.getppid()

執行檔案,輸出結果:
im father pid 32515 ppid 32015
father died..
im child 32516 1
           
  • 子程序已經被pid為1的init程序接收了,是以僵屍程序在這種情況下是不存在的,存在隻有孤兒程序而已,孤兒程序聲明周期結束自然會被init來銷毀。
               
三:僵屍程序危害場景:

  例如有個程序,它定期的産 生一個子程序,這個子程序需要做的事情很少,做完它該做的事情之後就退出了,是以這個子程序的生命周期很短,但是,父程序隻管生成新的子程序,至于子程序 退出之後的事情,則一概不聞不問,這樣,系統運作上一段時間之後,系統中就會存在很多的僵死程序,倘若用ps指令檢視的話,就會看到很多狀态為Z的程序。 嚴格地來說,僵死程序并不是問題的根源,罪魁禍首是産生出大量僵死程序的那個父程序。是以,當我們尋求如何消滅系統中大量的僵死程序時,答案就是把産生大 量僵死程序的那個元兇槍斃掉(也就是通過kill發送SIGTERM或者SIGKILL信号啦)。槍斃了元兇程序之後,它産生的僵死程序就變成了孤兒進 程,這些孤兒程序會被init程序接管,init程序會wait()這些孤兒程序,釋放它們占用的系統程序表中的資源,這樣,這些已經僵死的孤兒程序 就能瞑目而去了。

四:測試
           
#1、産生僵屍程序的程式test.py内容如下

#coding:utf-8
from multiprocessing import Process
import time,os

def run():
    print('子',os.getpid())

if __name__ == '__main__':
    p=Process(target=run)
    p.start()
    
    print('主',os.getpid())
    time.sleep(1000)
           
#2、在unix或linux系統上執行
[[email protected] ~]# python3  test.py &
[1] 18652
[[email protected] ~]# 主 18652
子 18653

[[email protected] ~]# ps aux |grep Z
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     18653  0.0  0.0      0     0 pts/0    Z    20:02   0:00 [python3] <defunct> #出現僵屍程序
root     18656  0.0  0.0 112648   952 pts/0    S+   20:02   0:00 grep --color=auto Z

[[email protected] ~]# top #執行top指令發現1zombie
top - 20:03:42 up 31 min,  3 users,  load average: 0.01, 0.06, 0.12
Tasks:  93 total,   2 running,  90 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1016884 total,    97184 free,    70848 used,   848852 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   782540 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
root      20   0   29788   1256    988 S  0.3  0.1   0:01.50 elfin                                                                                                                      

           
#3、
等待父程序正常結束後會調用wait/waitpid去回收僵屍程序
但如果父程序是一個死循環,永遠不會結束,那麼該僵屍程序就會一直存在,僵屍程序過多,就是有害的
解決方法一:殺死父程序
解決方法二:對開啟的子程序應該記得使用join,join會回收僵屍程序
參考python2源碼注釋
class Process(object):
    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _current_process._children.discard(self)

join方法中調用了wait,告訴系統釋放僵屍程序。discard為從自己的children中剔除
           

五.問題:

from multiprocessing import Process
import time,os

def task():
    print('%s is running' %os.getpid())
    time.sleep(3)
    
if __name__ == '__main__':
    p=Process(target=task)
    p.start()
    p.join() # 等待程序p結束後,join函數内部會發送系統調用wait,去告訴作業系統回收掉程序p的id号

    print(p.pid) #???此時能否看到子程序p的id号
    print('主')
           

答案:

#答案:可以
#分析:
p.join()是像作業系統發送請求,告知作業系統p的id号不需要再占用了,回收就可以,
此時在父程序内還可以看到p.pid,但此時的p.pid是一個無意義的id号,因為作業系統已經将該編号回收

打個比方:
我黨相當于作業系統,控制着整個中國的硬體,每個人相當于一個程序,每個人都需要跟我黨申請一個身份證号
該号碼就相當于程序的pid,人死後應該到我黨那裡登出身份證号,p.join()就相當于要求我黨回收身份證号,但p的家人(相當于主程序)
仍然持有p的身份證,但此刻的身份證已經沒有意義