1. 程式人生 > >如何創建進程及進程的特性

如何創建進程及進程的特性

發現 gif bsp time模塊 load lose 自己的 們的 內容

1.必備知識點,

  什麽是進程:程序被執行的過程,就叫進程。計算機內部原理,由操作系統控制硬盤操作將程序讀入內存,調用cpu來執行程序。內存中進程與進程之間的內存空間是絕對物理意義上的隔離的。啟動qq,微信,word三個程序,就會在內存中開辟三塊獨立的內存空間來存放它們,而如果一個程序被你啟動了好幾次(鼠標點了好幾下)那麽也是不同的進程,在內存中也是對應有互相隔離的內存空間——即進程與進程之間內存空間也是彼此隔離的(無論是不同的程序之間還是相同的程序亦或是下面介紹的父程序申請創建主程序方式~)

2.創建子進程的兩種方式

PS:在程序中再創建子進程的意義在於,想讓程序中的任務能夠並發去執行!

第一種:

from multiprocessing import Process
import time

def task():
    print(子程序開始運行》》》)
    time.sleep(3)
    print(主程序運行完畢》》》)

if __name__ == __main__:
    p=Process(target=task)
    p.start()
    print(主程序代碼讀取完畢!)

#運行結果
# 主程序代碼讀取完畢!
# 子程序開始運行》》》
    #間隔三秒後打印下面的結果
# 主程序運行完畢》》》
技術分享圖片
首先導入的是專門用來產生子進程的模塊Process是一個類
導入的time模塊是為了模擬子進程執行了一段時間
if __name__==__main__: 先調用Process類傳入參數產生子進程對象 在windows環境下創建子進程的代碼必須寫在上述條件下面, 因為程序值執行到p.start()時,其實就是向操作系統發出申請說我需要開一個子進程 這個操作系統會開辟單獨的內存空間,之後重新導入當前文件並運行,將產生的名稱空間作為子進程的初始數據庫放入新開辟的內存空間中(無論是windows系統還是linux系統,子進程的產生都需要拷貝父進程的數據作為起始運行狀態), 當主程序發出申請代碼塊後,就不會再去管子進程什麽時候創建運行(程序也決定不了這個事,而是由操作系統說了算),而繼續去運行自身接下來的代碼,
上述代碼解析

第二種:

from multiprocessing import Process
import time
class Myprocess(Process):
    def __init__(self,name):
        super(Myprocess,self).__init__()
        self.name=name
    def run(self):
        print(%s is running %self.name)
        time.sleep(3)
        print(%s is running over%self.name)
if __name__ == __main__:
    p=Myprocess(alex)
    p.start()
    print(主程序代碼運行完畢)
技術分享圖片
既然用來產生子進程的模塊是一個類,那麽我們學過面向對象及元類,對類的使用應該也是有一定水平的,我們就可以自己為子進程定制一些專屬的特性(後面會寫一個為子進程定制一個同時獲取子進程和父進程pid的小方法)
首先定義我們自己的類Myprocess繼承Process,在調用super()先繼承父類的初始化方法,在給子進程添加一個名字屬性
而下面的run()方法是必須要加的,也就是說每一個用來產生子進程的類中肯定對應有一個run()方法,這個run()對應的就是對象調用start()時觸發,run()方法裏寫的就是你想讓子進程想幹的事情,就是第一種創建子進程的task函數啦~
上述代碼解讀

3.驗證父進程與子進程之間空間是絕對物理意義上的互相隔離

from multiprocessing import Process
x=1000
def task():
    global x
    x=1
    print(子進程運行完畢)

if __name__ == __main__:
    p=Process(target=task)
    p.start()
    p.join()
    print(主進程的變量X:%s%x)
    #result:
            子進程運行完畢
            主進程的變量X:1000  
#這裏先在父程序名稱空間中定義一個全局變量x=1000,如何產生一個子進程,子進程會拷貝父類代碼名稱空間,
#這個時候在子進程函數方法中通過global方法將x的修改變為全局有效,這個時候運行完結果是父程序中變量的值並沒有改變,說明子進程並不能影響到父進程的名稱空間,
#這裏子進程中的修改操作確實執行了,只不過子進程修改的是子進程自己的名稱空間中的x的值

4.父進程在執行帶申請創建子進程後原地等待子進程運行結束再去運行自身下面的代碼~(join()方法)‘

from multiprocessing import Process
import time
def task():
    print(子程序開始運行)
    time.sleep(3)
    print(子程序運行完畢)
if __name__ == __main__:
    p=Process(target=task)
    p.start()
    p.join()
    print(主程序代碼讀取完畢!)
#結果:
#子程序開始運行
# 子程序運行完畢
# 主程序代碼讀取完畢!
#這裏的join()就是讓主程序等著子進程運行結束才能繼續往下走,join()在這裏並不僅僅是這個效果,起始涉及到了後面要說的僵屍進程和孤兒進程(針對uniux系統),
join()是為了將子進程運行完畢後產生的數據全部回收清空(了解即可)

這裏需要明確的是join()是讓誰等?是主進程等,卡住的是主進程而絕非其他的進程~~~

5.獲取進程的PID(getpid(),getppid())

  在計算機每個進程的運行計算機都會隨機分配給進程一個PID號,相當於我們的身份證一樣,每臺計算機中pid號的數量是有限的,那如何獲取子進程的pid號呢?

from multiprocessing import Process
import time,os
x=1000
def task():
    print(self:%s,parent:%s%(os.getpid(),os.getppid()))
    time.sleep(3)
if __name__ == __main__:
 p1=Process(target=task)
 p1.start()
 print(p1.pid)#在外部獲取子進程的pid,直接調子進程自帶的屬性,這裏可以自己為其定制,通過第二種創建子進程的方式自定義類!
 print(主程序的:%s%os.getpid())
#結果:
10144
主程序的:10724
self:10144,parent:10724

6.僵屍進程與孤兒進程(針對uniux,linux系統)

技術分享圖片
參考博客:http://www.cnblogs.com/Anker/p/3271773.html

一:僵屍進程(有害)
  僵屍進程:一個進程使用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系統上執行
[root@vm172-31-0-19 ~]# python3  test.py &
[1] 18652
[root@vm172-31-0-19 ~]# 主 18652
子 18653

[root@vm172-31-0-19 ~]# 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

[root@vm172-31-0-19 ~]# 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中剔除

解決方法三:http://blog.csdn.net/u010571844/article/details/50419798
僵屍進程與孤兒進程

  其實不管實在哪個操作平臺上,子進程都會經歷僵屍進程這一階段,而主進程在沒有可以幹擾他的代碼讀取情況下,大家會發現,主進程雖然代碼讀取完畢了,但是它還是不會結束,而是要等著所有的子進程全部結束才會結束,這就是為了回收子進程的屍體(數據)。詳細的請看上述縮進內容

如何創建進程及進程的特性