并发编程(多进程1)
一 multiprocessing模塊介紹
? ? python中的多線程無法利用多核優(yōu)勢,如果想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分情況需要使用多進(jìn)程。Python提供了multiprocessing。
? ? multiprocessing模塊用來開啟子進(jìn)程,并在子進(jìn)程中執(zhí)行我們定制的任務(wù)(比如函數(shù)),該模塊與多線程模塊threading的編程接口類似。
?multiprocessing模塊的功能眾多:支持子進(jìn)程、通信和共享數(shù)據(jù)、執(zhí)行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。
? ? 需要再次強(qiáng)調(diào)的一點是:與線程不同,進(jìn)程沒有任何共享狀態(tài),進(jìn)程修改的數(shù)據(jù),改動僅限于該進(jìn)程內(nèi)。
二 Process類的介紹
? ??創(chuàng)建進(jìn)程的類:
Process([group [, target [, name [, args [, kwargs]]]]]),由該類實例化得到的對象,表示一個子進(jìn)程中的任務(wù)(尚未啟動)強(qiáng)調(diào): 1. 需要使用關(guān)鍵字的方式來指定參數(shù) 2. args指定的為傳給target函數(shù)的位置參數(shù),是一個元組形式,必須有逗號? ??參數(shù)介紹:
1 group參數(shù)未使用,值始終為None 2 3 target表示調(diào)用對象,即子進(jìn)程要執(zhí)行的任務(wù) 4 5 args表示調(diào)用對象的位置參數(shù)元組,args=(1,2,'egon',) 6 7 kwargs表示調(diào)用對象的字典,kwargs={'name':'egon','age':18} 8 9 name為子進(jìn)程的名稱?方法介紹:
1 p.start():啟動進(jìn)程,并調(diào)用該子進(jìn)程中的p.run() 2 p.run():進(jìn)程啟動時運行的方法,正是它去調(diào)用target指定的函數(shù),我們自定義類的類中一定要實現(xiàn)該方法 3 4 p.terminate():強(qiáng)制終止進(jìn)程p,不會進(jìn)行任何清理操作,如果p創(chuàng)建了子進(jìn)程,該子進(jìn)程就成了僵尸進(jìn)程,使用該方法需要特別小心這種情況。如果p還保存了一個鎖那么也將不會被釋放,進(jìn)而導(dǎo)致死鎖5 p.is_alive():如果p仍然運行,返回True6 7 p.join([timeout]):主線程等待p終止(強(qiáng)調(diào):是主線程處于等的狀態(tài),而p是處于運行的狀態(tài))。timeout是可選的超時時間,需要強(qiáng)調(diào)的是,p.join只能join住start開啟的進(jìn)程,而不能join住run開啟的進(jìn)程? ??屬性介紹:
1 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è)置 2 3 p.name:進(jìn)程的名稱 4 5 p.pid:進(jìn)程的pid 6 7 p.exitcode:進(jìn)程在運行時為None、如果為–N,表示被信號N結(jié)束(了解即可) 8 9 p.authkey:進(jìn)程的身份驗證鍵,默認(rèn)是由os.urandom()隨機(jī)生成的32字符的字符串。這個鍵的用途是為涉及網(wǎng)絡(luò)連接的底層進(jìn)程間通信提供安全性,這類連接只有在具有相同的身份驗證鍵時才能成功(了解即可)三 Process類的使用
注意:在windows中Process()必須放到# if __name__ == '__main__':下
Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module.If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources).
This is the reason for hiding calls to Process() inside
if __name__ == "__main__"
since statements inside this if-statement will not get called upon import.
由于Windows沒有fork,多處理模塊啟動一個新的Python進(jìn)程并導(dǎo)入調(diào)用模塊。
如果在導(dǎo)入時調(diào)用Process(),那么這將啟動無限繼承的新進(jìn)程(或直到機(jī)器耗盡資源)。
這是隱藏對Process()內(nèi)部調(diào)用的原,使用if __name__ == “__main __”,這個if語句中的語句將不會在導(dǎo)入時被調(diào)用。
詳細(xì)解釋
3.1創(chuàng)建開啟子進(jìn)程的兩種方式
from multiprocessing import Processimport time
def task(name):
??? print('%s is runing' %(name))
??? time.sleep(3)
??? print('%s is done' % (name))
if __name__ == '__main__':
??? p = Process(target=task,args=('太白金星',))
??? # p = Process(target=task,kwargs={'name':'太白金星'})? 兩種傳參方式
??? p.start()
??? print('====主') from multiprocessing import Process
import time
# 方式二:
class MyProcess(Process):
??? def __init__(self,name):
???????
??????? self.name = name
??????? super().__init__()
???????
??? def run(self):? # 必須定義一個run方法
??????? print('%s is runing' % (self.name))
??????? time.sleep(3)
??????? print('%s is done' % (self.name))
???
???
if __name__ == '__main__':
??? p = MyProcess('太白金星')
??? p.start()
??? print('===主')
3.2驗證進(jìn)程之間的空間隔離
# 接下來我們驗證一下進(jìn)程之間的互相隔離。# 在一個進(jìn)程中
x = 1000
#
# def task():
#???? global x
#???? x = 2
#
# task()
# print(x)
# 在不同的進(jìn)程中:
# from multiprocessing import Process
# import time
# x = 1000
#
# def task():
#???? global x
#???? x = 2
#
# if __name__ == '__main__':
#???? p = Process(target=task)
#???? p.start()
#???? time.sleep(3)
#???? print(x)
3.3 進(jìn)程對象的join方法
from multiprocessing import Process import time# 父進(jìn)程等待子進(jìn)程結(jié)束之后在執(zhí)行 # 方法一 加sleep 不可取!# def task(n): # time.sleep(3) # print('子進(jìn)程結(jié)束....') # # if __name__ == '__main__': # p = Process(target=task,args=('太白金星',)) # p.start() # time.sleep(5) # print('主進(jìn)程開始運行....') # # 這樣雖然達(dá)到了目的, # 1,但是你在程序中故意加sleep極大影響程序的效率。 # 2,sleep(3)只是虛擬子進(jìn)程運行的時間,子進(jìn)程運行完畢的時間是不固定的。# 方法二: join# from multiprocessing import Process # import time # # # def task(n): # time.sleep(3) # print('子進(jìn)程結(jié)束....') # # # if __name__ == '__main__': # p = Process(target=task,args=('太白金星',)) # p.start() # p.join() # 等待p這個子進(jìn)程運行結(jié)束之后,在執(zhí)行下面的代碼(主進(jìn)程). # print('主進(jìn)程開始運行....') # # 接下來我要開啟十個子進(jìn)程,先看看效果# from multiprocessing import Process # import time # # def task(n): # print('%s is running' %n) # # if __name__ == '__main__': # for i in range(1, 11): # p = Process(target=task,args=(i,)) # p.start() # ''' # 我這里是不是運行十個子進(jìn)程之后,才會運行主進(jìn)程?當(dāng)然不會!!! # 1,p.start()只是向操作系統(tǒng)發(fā)送一個請求而已,剩下的操作系統(tǒng)在內(nèi)存開啟進(jìn)程空間,運行進(jìn)程程序不一定是馬上執(zhí)行。 # 2,開啟進(jìn)程的開銷是比較大的。 # ''' # print('主進(jìn)程開始運行....')# 那么有人說,老師我對這個不理解,我給你拆解開來。# from multiprocessing import Process # import time # # def task(n): # print('%s is running' %n) # # if __name__ == '__main__': # p1 = Process(target=task,args=(1,)) # p2 = Process(target=task,args=(2,)) # p3 = Process(target=task,args=(3,)) # p4 = Process(target=task,args=(4,)) # p5 = Process(target=task,args=(5,)) # # p1.start() # p2.start() # p3.start() # p4.start() # p5.start() # # print('主進(jìn)程開始運行....')# 接下來 實現(xiàn)起多子個進(jìn)程,然后等待這些子進(jìn)程都結(jié)束之后,在開啟主進(jìn)程。# from multiprocessing import Process # import time # # def task(n): # time.sleep(3) # print('%s is running' %n) # # if __name__ == '__main__': # start_time = time.time() # p1 = Process(target=task,args=(1,)) # p2 = Process(target=task,args=(2,)) # p3 = Process(target=task,args=(3,)) # # 幾乎同一個時刻發(fā)送三個請求 # p1.start() # p2.start() # p3.start() # # 對著三個自己成使用三個join # # p1.join() # p2.join() # p3.join() # # print(time.time() - start_time,'主進(jìn)程開始運行....') # # 3s 多一點點這是來回切換的所用時間。# 那么在進(jìn)行舉例:# from multiprocessing import Process # import time # # def task(n): # time.sleep(n) # print('%s is running' %n) # # if __name__ == '__main__': # start_time = time.time() # p1 = Process(target=task,args=(1,)) # p2 = Process(target=task,args=(2,)) # p3 = Process(target=task,args=(3,)) # # 幾乎同一個時刻發(fā)送三個請求 # p1.start() # p2.start() # p3.start() # # 對著三個自己成使用三個join # # p1.join() # 1s # p2.join() # 2s # p3.join() # 3s # # print(time.time() - start_time,'主進(jìn)程開始運行....')# 3s 多一點點這是來回切換的所用時間。# 利用for循環(huán)精簡上面的示例:# from multiprocessing import Process # import time # # def task(n): # time.sleep(1) # print('%s is running' %n) # # if __name__ == '__main__': # start_time = time.time() # # for i in range(1,4): # # p = Process(target=task,args=(i,)) # # p.start() # # p.join() # # p1 = Process(target=task,args=(1,)) # p2 = Process(target=task,args=(2,)) # p3 = Process(target=task,args=(3,)) # # 幾乎同一個時刻發(fā)送三個請求 # p1.start() # p1.join() # p2.start() # p2.join() # p3.start() # p3.join() # # 上面的代碼,p1.join()他的作用:你的主進(jìn)程代碼必須等我的p1子進(jìn)程執(zhí)行完畢之后,在執(zhí)行 # # p2.start()這個命令是主進(jìn)程的代碼。 # # 而 如果你這樣寫: # ''' # p1.join() # p2.join() # p3.join() # ''' # # print(time.time() - start_time,'主進(jìn)程開始運行....')# 所以你上面的代碼應(yīng)該怎么寫?# from multiprocessing import Process # import time # # def task(n): # time.sleep(3) # print('%s is running' %n) # # if __name__ == '__main__': # p_l = [] # start_time = time.time() # for i in range(1,4): # p = Process(target=task,args=(i,)) # p.start() # p_l.append(p) # # 對著三個自己成使用三個join # for i in p_l: # i.join() # print(time.time() - start_time,'主進(jìn)程開始運行....')3.4 進(jìn)程對象的其他屬性(了解)
# from multiprocessing import Process# import time
# import os
#
# def task(n):
#???? time.sleep(3)
#???? print('%s is running' %n,os.getpid(),os.getppid())
#
# if __name__ == '__main__':
#???? p1 = Process(target=task,args=(1,),name = '任務(wù)1')
#???? # print(p1.name) # 給子進(jìn)程起名字
#???? # for i in range(3):
#???? #???? p = Process(target=task, args=(1,))
#???? #???? print(p.name)? # 給子進(jìn)程起名字
#???? p1.start()
#???? # p1.terminate()
#???? # time.sleep(2)? # 睡一會,他就將我的子進(jìn)程殺死了。
#???? # print(p1.is_alive())? # False
#???? print(p1.pid)
#???? # print('主')
#???? print(os.getpid())
3.5 僵尸進(jìn)程與孤兒進(jìn)程
一:僵尸進(jìn)程(有害)僵尸進(jìn)程:一個進(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)信息。
因此,UNⅨ提供了一種機(jī)制可以保證父進(jìn)程可以在任意時刻獲取子進(jìn)程結(jié)束時的狀態(tài)信息:
1、在每個進(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等)
2、直到父進(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)程:一個父進(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)程并不會有什么危害。
我們來測試一下(創(chuàng)建完子進(jìn)程后,主進(jìn)程所在的這個腳本就退出了,當(dāng)父進(jìn)程先于子進(jìn)程結(jié)束時,子進(jìn)程會被init收養(yǎng),成為孤兒進(jìn)程,而非僵尸進(jìn)程),文件內(nèi)容
import os
import sys
import time
pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
#執(zhí)行pid=os.fork()則會生成一個子進(jìn)程
#返回值pid有兩種值:
#??? 如果返回的pid值為0,表示在子進(jìn)程當(dāng)中
#??? 如果返回的pid值>0,表示在父進(jìn)程當(dāng)中
if pid > 0:
??? print 'father died..'
??? sys.exit(0)
# 保證主線程退出完畢
time.sleep(1)
print 'im child', os.getpid(), os.getppid()
執(zhí)行文件,輸出結(jié)果:
im father pid 32515 ppid 32015
father died..
im child 32516 1
看,子進(jìn)程已經(jīng)被pid為1的init進(jìn)程接收了,所以僵尸進(jìn)程在這種情況下是不存在的,存在只有孤兒進(jìn)程而已,孤兒進(jìn)程聲明周期結(jié)束自然會被init來銷毀。
三:僵尸進(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)程 就能瞑目而去了。
四:測試
#1、產(chǎn)生僵尸進(jìn)程的程序test.py內(nèi)容如下
#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系統(tǒng)上執(zhí)行
[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> #出現(xiàn)僵尸進(jìn)程
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 #執(zhí)行top命令發(fā)現(xiàn)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、
等待父進(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)程
參考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方法中調(diào)用了wait,告訴系統(tǒng)釋放僵尸進(jìn)程。discard為從自己的children中剔除 解決方法三:http://blog.csdn.net/u010571844/article/details/50419798
四 守護(hù)進(jìn)程
主進(jìn)程創(chuàng)建守護(hù)進(jìn)程
其一:守護(hù)進(jìn)程會在主進(jìn)程代碼執(zhí)行結(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)程隨即終止
from multiprocessing import Processimport time
import random
class Piao(Process):
??? def __init__(self,name):
??????? self.name=name
??????? super().__init__()
??? def run(self):
??????? print('%s is piaoing' %self.name)
??????? time.sleep(random.randrange(1,3))
??????? print('%s is piao end' %self.name)
p=Piao('egon')
p.daemon=True #一定要在p.start()前設(shè)置,設(shè)置p為守護(hù)進(jìn)程,禁止p創(chuàng)建子進(jìn)程,并且父進(jìn)程代碼執(zhí)行結(jié)束,p即終止運行
p.start()
print('主') #主進(jìn)程代碼運行完畢,守護(hù)進(jìn)程就會結(jié)束
from multiprocessing import Process
from threading import Thread
import time
def foo():
??? print(123)
??? time.sleep(1)
??? print("end123")
def bar():
??? print(456)
??? time.sleep(3)
??? print("end456")
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True
p1.start()
p2.start()
print("main-------") #打印該行則主進(jìn)程代碼結(jié)束,則守護(hù)進(jìn)程p1應(yīng)該被終止,可能會有p1任務(wù)執(zhí)行的打印信息123,因為主進(jìn)程打印main----時,p1也執(zhí)行了,但是隨即被終止
?
轉(zhuǎn)載于:https://www.cnblogs.com/YZL2333/p/10445981.html
總結(jié)
以上是生活随笔為你收集整理的并发编程(多进程1)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: iOS学习系列 - 扩展机制catego
- 下一篇: C#反射之Assembly.Load,A
