什么是進(jìn)程
- 進(jìn)程:一個程序的執(zhí)行過程或者一個任務(wù)。
- 并發(fā): 是偽并行,看起來是同時運行,其實通過單個cpu+多道技術(shù)就可以實現(xiàn)并發(fā)。
- 并行: 同時運行,只有具備多個cpu才能實現(xiàn)并行。
新進(jìn)程的創(chuàng)建都是由一個已經(jīng)存在的進(jìn)程執(zhí)行了一個用于創(chuàng)建進(jìn)程的系統(tǒng)調(diào)用而創(chuàng)建的:
在UNIX中系統(tǒng)調(diào)用是:fork。 fork 會創(chuàng)建和父進(jìn)程一模一樣的副本,在初始狀態(tài),二者有相同的存儲映像和數(shù)據(jù),同樣的環(huán)境字符串和同樣的打開文件(在shell解釋器進(jìn)程中,執(zhí)行一個命令就會創(chuàng)建一個子進(jìn)程)
在windows中,該系統(tǒng)調(diào)用的是 CreateProcess, CreateProcess既處理進(jìn)程的創(chuàng)建,也負(fù)責(zé)把正確的程序裝入新的進(jìn)程。
關(guān)于創(chuàng)建進(jìn)程,UNIX和Windows:
相同的是:進(jìn)程創(chuàng)建后,父進(jìn)程和子進(jìn)程有各自不同的內(nèi)存地址空間(多道技術(shù)要求物理層面實現(xiàn)進(jìn)程之間的內(nèi)存隔離),任何一個進(jìn)程修改自身的空間數(shù)據(jù)都不會影響到另一個進(jìn)程。
不同的是:在UNIX 中,子進(jìn)程的初始地址空間是父進(jìn)程的一個副本,子進(jìn)程和父進(jìn)程是可以有只讀的共享存儲區(qū)。但是在Windows中,從一開始,父進(jìn)程和子進(jìn)程的地址空間就是不同的。
開啟進(jìn)程的兩種方式
方式一
開啟進(jìn)程(使用提供的模塊):
# coding=utf-8
from multiprocessing import Process # 進(jìn)程模塊
import time
def task(name):
print('%s is running' %name)
time.sleep(2)
print('%s is done' %name)
if __name__ == '__main__': # 在Windows系統(tǒng)上,進(jìn)程開啟前需要先定義進(jìn)程執(zhí)行函數(shù),進(jìn)程的執(zhí)行代碼必須放在開啟進(jìn)程代碼前
p=Process(target=task,args=('cat',)) # target 指定進(jìn)程函數(shù),args指定參數(shù),參數(shù)必須為元組
p.start()
print('主')
輸出結(jié)果:
主
cat is running
cat is done
方式二
自定義進(jìn)程:
# coding=utf-8
from multiprocessing import Process
import time
class MyProcess(Process): # 自定義類,繼承系統(tǒng)的Process
def __init__(self,name):
super(MyProcess,self).__init__()
self.name=name
def run(self):
print('%s is running' %self.name)
time.sleep(2)
print('%s is done' %self.name)
if __name__ == '__main__':
p=MyProcess('進(jìn)程1')
p.start() # 調(diào)用 run() 函數(shù)
print('主')
輸出結(jié)果:
主
進(jìn)程1 is running
進(jìn)程1 is done
進(jìn)程的隔離
創(chuàng)建進(jìn)程的過程中,子進(jìn)程在初始狀態(tài)會拷貝父進(jìn)程中的數(shù)據(jù),但是在自己運行過程中,修改子進(jìn)程自己的數(shù)據(jù)不會影響父進(jìn)程的數(shù)據(jù),進(jìn)程之間的數(shù)據(jù)是相互隔離的:
# coding=utf-8
from multiprocessing import Process # 進(jìn)程模塊
import time
n = 100
def task(name):
global n
n = 0
time.sleep(2)
print('子進(jìn)程',n)
if __name__ == '__main__':
p=Process(target=task,args=('cat',))
p.start()
p.join() # 等待子進(jìn)程運行完畢后再運行后面主進(jìn)程的代碼
print('主進(jìn)程',n)
輸出結(jié)果:
子進(jìn)程 0
主進(jìn)程 100
進(jìn)程相關(guān)的方法
join 方法
# coding=utf-8
import os
from multiprocessing import Process # 進(jìn)程模塊
import time
def task(n):
print('%s is running' %os.getpid())
time.sleep(n)
print('%s is done' %os.getpid())
if __name__ == '__main__':
p1 = Process(target=task,args=(1,))
p2 = Process(target=task,args=(2,))
p3 = Process(target=task,args=(3,))
start_time=time.time() # 開啟進(jìn)程計時
p1.start()
p2.start()
p3.start()
p1.join() # 等待1s p1結(jié)束
p2.join() # 等待1s p2結(jié)束
p3.join() # 等待1s p3結(jié)束
stop_time=time.time() # 計時結(jié)束
print('主進(jìn)程',(stop_time-start_time))
運行結(jié)果:
16188 is running
228 is running
13480 is running
16188 is done
228 is done
13480 is done
主進(jìn)程 3.086230516433716
可以改寫以上代碼:
# coding=utf-8
import os
from multiprocessing import Process # 進(jìn)程模塊
import time
def task(n):
print('%s is running' %os.getpid())
time.sleep(n)
print('%s is done' %os.getpid())
if __name__ == '__main__':
p1 = Process(target=task,args=(1,))
p2 = Process(target=task,args=(2,))
p3 = Process(target=task,args=(3,))
start_time=time.time() # 開啟進(jìn)程計時
p_list=[p1,p2,p3]
for p in p_list:
p.start()
for p in p_list:
p.join()
stop_time=time.time() # 計時結(jié)束
print('主進(jìn)程',(stop_time-start_time))
如果要改為串行執(zhí)行,可以用如下方式:
# coding=utf-8
import os
from multiprocessing import Process # 進(jìn)程模塊
import time
def task(n):
print('%s is running' %os.getpid())
time.sleep(n)
print('%s is done' %os.getpid())
if __name__ == '__main__': # 如果串行執(zhí)行,可以不調(diào)用此處的方法
p1 = Process(target=task,args=(1,))
p2 = Process(target=task,args=(2,))
p3 = Process(target=task,args=(3,))
start_time=time.time() # 開啟進(jìn)程計時
task(1)
task(2)
task(3)
stop_time=time.time() # 計時結(jié)束
print('主進(jìn)程',(stop_time-start_time))
輸出結(jié)果:
12116 is running
12116 is done
12116 is running
12116 is done
12116 is running
12116 is done
主進(jìn)程 6.001911163330078
其它常見方法
- p.start(): 啟動進(jìn)程,并調(diào)用子進(jìn)程中的p.run()
- p.run(): 進(jìn)程啟動時運行的方法,正是它去調(diào)用target指定的函數(shù),自定義的類中一定要實現(xiàn)該方法。
- p.terminate(): 強(qiáng)制終止進(jìn)程p,不會進(jìn)行任何清理操作,如果p 創(chuàng)建了子進(jìn)程,該子進(jìn)程就成了僵尸進(jìn)程,使用該方法需要特別小心這種情況,如果P還保存了一個鎖那么也不會被釋放,進(jìn)而導(dǎo)致死鎖。
- p.is_alive(): 如果p仍然運行,返回True
- p.join([timeout]): 主線程等待p終止(p處于運行的狀態(tài),主線程處于等待狀態(tài))。timeout是可選的超時時間。p.join只能join住start開啟的進(jìn)程,不能join住run開啟的進(jìn)程
進(jìn)程屬性
- p.daemon: 默認(rèn)值為False,如果設(shè)置為True,代表p為后臺運行的守護(hù)進(jìn)程,當(dāng)p的父進(jìn)程終止時,p也隨之終止,并設(shè)定為True之后,P不能創(chuàng)建自己的新進(jìn)程,必須在p.start()之前設(shè)置
- p.name: 進(jìn)程的名稱
- p.pid: 進(jìn)程的pid
- p.exitcode: 進(jìn)程在運行時為None,如果為-N,表示被信號N結(jié)束
- p.authkey: 進(jìn)程的身份驗證鍵,默認(rèn)是由os.urandom()隨機(jī)生成的32字符的字符串。這個鍵的用途是為涉及網(wǎng)絡(luò)連接的底層進(jìn)程通信提供安全性,這類連接只有在?相同的身份驗證鍵時才能成功
僵尸進(jìn)程和孤兒進(jìn)程
僵尸進(jìn)程
在UNIX中,一個進(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/linux中,正常情況下子進(jìn)程是通過父進(jìn)程創(chuàng)建的,子進(jìn)程在創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運行是一個異步過程,即父進(jìn)程永遠(yuǎn)無法預(yù)測子進(jìn)程到底什么時候結(jié)束,如果子進(jìn)程一結(jié)束就立刻回收其全部資源,那么在父進(jìn)程內(nèi)將無法獲取子進(jìn)程的狀態(tài)信息。
因此,UNIX提供了一種機(jī)制可以保證父進(jìn)程可以在任意時刻獲取子進(jìn)程結(jié)束時的狀態(tài)信息:
- 在每個進(jìn)程退出的時候,內(nèi)核釋放該進(jìn)程所有的資源,包括打開的文件,占用的內(nèi)存等。但是仍然為其保留一定的信息(包括進(jìn)程號the process ID,退出狀態(tài)the termination status of the process,運行時間the amount of CPU time taken by the process等)
- 直到父進(jìn)程通過wait / waitpid來取時才釋放. 但這樣就導(dǎo)致了問題,如果進(jìn)程不調(diào)用wait / waitpid的話,那么保留的那段信息就不會釋放,其進(jìn)程號就會一直被占用,但是系統(tǒng)所能使用的進(jìn)程號是有限的,如果大量的產(chǎn)生僵死進(jìn)程,將因為沒有可用的進(jìn)程號而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程. 此即為僵尸進(jìn)程的危害,應(yīng)當(dāng)避免。
任何一個子進(jìn)程(init除外)在exit()之后,并非馬上就消失掉,而是留下一個稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu),等待父進(jìn)程處理。這是每個子進(jìn)程在結(jié)束時都要經(jīng)過的階段。如果子進(jìn)程在exit()之后,父進(jìn)程沒有來得及處理,這時用ps命令就能看到子進(jìn)程的狀態(tài)是“Z”。如果父進(jìn)程能及時 處理,可能用ps命令就來不及看到子進(jìn)程的僵尸狀態(tài),但這并不等于子進(jìn)程不經(jīng)過僵尸狀態(tài)。 如果父進(jìn)程在子進(jìn)程結(jié)束之前退出,則子進(jìn)程將由init接管。init將會以父進(jìn)程的身份對僵尸狀態(tài)的子進(jìn)程進(jìn)行處理。
僵尸進(jìn)程的危害場景
例如有個進(jìn)程,它定期的產(chǎn)生一個子進(jìn)程,這個子進(jìn)程需要做的事情很少,做完它該做的事情之后就退出了,因此這個子進(jìn)程的生命周期很短,但是,父進(jìn)程只管生成新的子進(jìn)程,至于子進(jìn)程 退出之后的事情,則一概不聞不問,這樣,系統(tǒng)運行上一段時間之后,系統(tǒng)中就會存在很多的僵尸進(jìn)程,倘若用ps命令查看的話,就會看到很多狀態(tài)為Z的進(jìn)程。 嚴(yán)格地來說,僵尸進(jìn)程并不是問題的根源,罪魁禍?zhǔn)资钱a(chǎn)生出大量僵死進(jìn)程的那個父進(jìn)程。因此,當(dāng)我們尋求如何消滅系統(tǒng)中大量的僵死進(jìn)程時,答案就是把產(chǎn)生大 量僵死進(jìn)程的那個元兇槍斃掉(也就是通過kill發(fā)送SIGTERM或者SIGKILL信號啦)。槍斃了元兇進(jìn)程之后,它產(chǎn)生的僵尸進(jìn)程就變成了孤兒進(jìn) 程,這些孤兒進(jìn)程會被init進(jìn)程接管,init進(jìn)程會wait()這些孤兒進(jìn)程,釋放它們占用的系統(tǒng)進(jìn)程表中的資源,這樣,這些已經(jīng)僵死的孤兒進(jìn)程 就能瞑目而去了
在unix系統(tǒng)中,執(zhí)行top命令中 zombie的數(shù)量就是當(dāng)前僵尸進(jìn)程的數(shù)量
回收僵尸進(jìn)程的解決辦法
等待父進(jìn)程正常結(jié)束后會調(diào)用wait/waitpid去回收僵尸進(jìn)程
但如果父進(jìn)程是一個死循環(huán),永遠(yuǎn)不會結(jié)束,那么該僵尸進(jìn)程就會一直存在,僵尸進(jìn)程過多,就是有害的
- 解決方法一:殺死父進(jìn)程
- 解決方法二:對開啟的子進(jìn)程應(yīng)該記得使用join,join會回收僵尸進(jìn)程
- 解決辦法三:python中有一個signal模塊,可以使用signal(SIGCHLD, SIG_IGN)處理僵尸進(jìn)程
孤兒進(jìn)程
一個父進(jìn)程退出,而它的一個或多個子進(jìn)程還在運行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號為1)所收養(yǎng),并由init進(jìn)程對它們完成狀態(tài)收集工作。
孤兒進(jìn)程是沒有父進(jìn)程的進(jìn)程,孤兒進(jìn)程這個重任就落到了init進(jìn)程身上,init進(jìn)程就好像是一個民政局,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作。每當(dāng)出現(xiàn)一個孤兒進(jìn)程的時候,內(nèi)核就把孤 兒進(jìn)程的父進(jìn)程設(shè)置為init,而init進(jìn)程會循環(huán)地wait()它的已經(jīng)退出的子進(jìn)程。這樣,當(dāng)一個孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時候,init進(jìn)程就會代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會有什么危害。
守護(hù)進(jìn)程
守護(hù)進(jìn)程是由主進(jìn)程創(chuàng)建的子進(jìn)程:
- 守護(hù)進(jìn)程會在主進(jìn)程代碼結(jié)束后就終止。
- 守護(hù)進(jìn)程內(nèi)無法再開啟子進(jìn)程,否則拋出異常:AssertionError: daemonic processes are not allowed to have children
- 進(jìn)程之間是相互獨立的,主進(jìn)程代碼運行結(jié)束,守護(hù)進(jìn)程隨即終止。
示例:
# coding=utf-8
import os
from multiprocessing import Process # 進(jìn)程模塊
import time
def task(n):
print('%s is running' %os.getpid())
time.sleep(2)
print('%s is done' %os.getpid())
if __name__ == '__main__':
p = Process(target=task,args=('ggg',))
p.daemon = True # 開啟守護(hù)進(jìn)程,必須在start之前設(shè)置
p.start()
print("主進(jìn)程")
輸出結(jié)果:
主進(jìn)程
說明: 由于開啟了守護(hù)進(jìn)程,當(dāng)主進(jìn)程運行結(jié)束時,子進(jìn)程(守護(hù)進(jìn)程)也會終止運行,不管守護(hù)進(jìn)程是否運行完畢。
守護(hù)進(jìn)程的使用場景: 當(dāng)子進(jìn)程執(zhí)行的任務(wù),在父進(jìn)程代碼運行完畢后就沒有運行的必要了,那么該子進(jìn)程就應(yīng)該設(shè)置為守護(hù)進(jìn)程。
守護(hù)進(jìn)程與普通子進(jìn)程測試:
# coding=utf-8
import os
from multiprocessing import Process # 進(jìn)程模塊
import time
def foo():
print('這是守護(hù)進(jìn)程')
time.sleep(1)
print('守護(hù)進(jìn)程結(jié)束')
def bar():
print('這是普通子進(jìn)程')
time.sleep(2)
print('bar 運行結(jié)束')
if __name__ == '__main__':
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True
p1.start()
p2.start()
print("主進(jìn)程到此結(jié)束!")
運行結(jié)果:
主進(jìn)程到此結(jié)束!
這是普通子進(jìn)程
bar 運行結(jié)束