Python并发编程之:多进程
一 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)的一點(diǎn)是:與線程不同,進(jìn)程沒有任何共享狀態(tài),進(jìn)程修改的數(shù)據(jù),改動僅限于該進(jìn)程內(nèi)。
二 Process類的介紹
? ??創(chuàng)建進(jìn)程的類:
Process([group [, target [, name [, args [, kwargs]]]]]),由該類實(shí)例化得到的對象,表示一個子進(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)程啟動時運(yùn)行的方法,正是它去調(diào)用target指定的函數(shù),我們自定義類的類中一定要實(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仍然運(yùn)行,返回True6 7 p.join([timeout]):主線程等待p終止(強(qiáng)調(diào):是主線程處于等的狀態(tài),而p是處于運(yùn)行的狀態(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為后臺運(yùn)行的守護(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)程在運(yùn)行時為None、如果為–N,表示被信號N結(jié)束(了解即可) 8 9 p.authkey:進(jìn)程的身份驗(yàn)證鍵,默認(rèn)是由os.urandom()隨機(jī)生成的32字符的字符串。這個鍵的用途是為涉及網(wǎng)絡(luò)連接的底層進(jìn)程間通信提供安全性,這類連接只有在具有相同的身份驗(yà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() insideif __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ì)解釋 詳細(xì)解釋3.1創(chuàng)建開啟子進(jìn)程的兩種方式
from multiprocessing import Process import 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 = namesuper().__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驗(yàn)證進(jìn)程之間的空間隔離
# 接下來我們驗(yà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) 代碼驗(yàn)證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)程開始運(yùn)行....') # # 這樣雖然達(dá)到了目的, # 1,但是你在程序中故意加sleep極大影響程序的效率。 # 2,sleep(3)只是虛擬子進(jìn)程運(yùn)行的時間,子進(jìn)程運(yù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)程運(yùn)行結(jié)束之后,在執(zhí)行下面的代碼(主進(jìn)程). # print('主進(jìn)程開始運(yù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() # ''' # 我這里是不是運(yùn)行十個子進(jìn)程之后,才會運(yùn)行主進(jìn)程?當(dāng)然不會!!! # 1,p.start()只是向操作系統(tǒng)發(fā)送一個請求而已,剩下的操作系統(tǒng)在內(nèi)存開啟進(jìn)程空間,運(yùn)行進(jìn)程程序不一定是馬上執(zhí)行。 # 2,開啟進(jìn)程的開銷是比較大的。 # ''' # print('主進(jìn)程開始運(yù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)程開始運(yùn)行....')# 接下來 實(shí)現(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)程開始運(yùn)行....') # # 3s 多一點(diǎn)點(diǎn)這是來回切換的所用時間。# 那么在進(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)程開始運(yùn)行....')# 3s 多一點(diǎn)點(diǎn)這是來回切換的所用時間。# 利用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ù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)程開始運(yùn)行....') 代碼實(shí)例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)程
參考博客:http://www.cnblogs.com/Anker/p/3271773.html一:僵尸進(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)程的運(yù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,運(yùn)行時間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)程,將因?yà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)程還在運(yù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 timepid = 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)運(yùn)行上一段時間之后,系統(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,osdef 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詳細(xì)了解 詳細(xì)了解四 守護(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)程之間是互相獨(dú)立的,主進(jìn)程代碼運(yùn)行結(jié)束,守護(hù)進(jìn)程隨即終止
from multiprocessing import Process import time import randomclass Piao(Process):def __init__(self,name):self.name=namesuper().__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即終止運(yùn)行 p.start() print('主') 代碼示例 #主進(jìn)程代碼運(yù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,因?yàn)橹鬟M(jìn)程打印main----時,p1也執(zhí)行了,但是隨即被終止 經(jīng)典例題五 進(jìn)程同步(鎖)
進(jìn)程之間數(shù)據(jù)不共享,但是共享同一套文件系統(tǒng),所以訪問同一個文件,或同一個打印終端,是沒有問題的,
而共享帶來的是競爭,競爭帶來的結(jié)果就是錯亂,如何控制,就是加鎖處理
#并發(fā)運(yùn)行,效率高,但競爭同一打印終端,帶來了打印錯亂 from multiprocessing import Process import os,time def work():print('%s is running' %os.getpid())time.sleep(2)print('%s is done' %os.getpid())if __name__ == '__main__':for i in range(3):p=Process(target=work)p.start()并發(fā)運(yùn)行,效率高,但競爭同一打印終端,帶來了打印錯亂 不加鎖,效率高但是順序容易錯亂 #由并發(fā)變成了串行,犧牲了運(yùn)行效率,但避免了競爭 from multiprocessing import Process,Lock import os,time def work(lock):lock.acquire()print('%s is running' %os.getpid())time.sleep(2)print('%s is done' %os.getpid())lock.release() if __name__ == '__main__':lock=Lock()for i in range(3):p=Process(target=work,args=(lock,))p.start()加鎖:由并發(fā)變成了串行,犧牲了運(yùn)行效率,但避免了競爭 加鎖處理,犧牲了效率,但是保證了順序上面這種情況雖然使用加鎖的形式實(shí)現(xiàn)了順序的執(zhí)行,但是程序又重新變成串行了,這樣確實(shí)會浪費(fèi)了時間,卻保證了數(shù)據(jù)的安全。
接下來,我們以模擬搶票為例,來看看數(shù)據(jù)安全的重要性。?
#文件db的內(nèi)容為:{"count":1} #注意一定要用雙引號,不然json無法識別 #并發(fā)運(yùn)行,效率高,但競爭寫同一文件,數(shù)據(jù)寫入錯亂 from multiprocessing import Process,Lock import time,json,random def search():dic=json.load(open('db'))print('\033[43m剩余票數(shù)%s\033[0m' %dic['count'])def get():dic=json.load(open('db'))time.sleep(0.1) #模擬讀數(shù)據(jù)的網(wǎng)絡(luò)延遲if dic['count'] >0:dic['count']-=1time.sleep(0.2) #模擬寫數(shù)據(jù)的網(wǎng)絡(luò)延遲json.dump(dic,open('db','w'))print('\033[43m購票成功\033[0m')def task():search()get()if __name__ == '__main__':for i in range(100): #模擬并發(fā)100個客戶端搶票p=Process(target=task)p.start()多進(jìn)程同時搶購余票 多進(jìn)程搶票 #文件db的內(nèi)容為:{"count":5} #注意一定要用雙引號,不然json無法識別 #并發(fā)運(yùn)行,效率高,但競爭寫同一文件,數(shù)據(jù)寫入錯亂 from multiprocessing import Process,Lock import time,json,random def search():dic=json.load(open('db'))print('\033[43m剩余票數(shù)%s\033[0m' %dic['count'])def get():dic=json.load(open('db'))time.sleep(random.random()) #模擬讀數(shù)據(jù)的網(wǎng)絡(luò)延遲if dic['count'] >0:dic['count']-=1time.sleep(random.random()) #模擬寫數(shù)據(jù)的網(wǎng)絡(luò)延遲json.dump(dic,open('db','w'))print('\033[32m購票成功\033[0m')else:print('\033[31m購票失敗\033[0m')def task(lock):search()lock.acquire()get()lock.release()if __name__ == '__main__':lock = Lock()for i in range(100): #模擬并發(fā)100個客戶端搶票p=Process(target=task,args=(lock,))p.start()使用鎖來保證數(shù)據(jù)安全 使用鎖保證數(shù)據(jù)安全 #加鎖可以保證多個進(jìn)程修改同一塊數(shù)據(jù)時,同一時間只能有一個任務(wù)可以進(jìn)行修改,即串行的修改,沒錯,速度是慢了,但犧牲了速度卻保證了數(shù)據(jù)安全。 雖然可以用文件共享數(shù)據(jù)實(shí)現(xiàn)進(jìn)程間通信,但問題是: 1.效率低(共享數(shù)據(jù)基于文件,而文件是硬盤上的數(shù)據(jù)) 2.需要自己加鎖處理#因此我們最好找尋一種解決方案能夠兼顧:1、效率高(多個進(jìn)程共享一塊內(nèi)存的數(shù)據(jù))2、幫我們處理好鎖問題。這就是mutiprocessing模塊為我們提供的基于消息的IPC通信機(jī)制:隊列和管道。 隊列和管道都是將數(shù)據(jù)存放于內(nèi)存中 隊列又是基于(管道+鎖)實(shí)現(xiàn)的,可以讓我們從復(fù)雜的鎖問題中解脫出來, 我們應(yīng)該盡量避免使用共享數(shù)據(jù),盡可能使用消息傳遞和隊列,避免處理復(fù)雜的同步和鎖問題,而且在進(jìn)程數(shù)目增多時,往往可以獲得更好的可獲展性。?
轉(zhuǎn)載于:https://www.cnblogs.com/Big-Dinosaur/p/10496386.html
總結(jié)
以上是生活随笔為你收集整理的Python并发编程之:多进程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二叉树总结—建树和4种遍历方式(递归非递
- 下一篇: hdu 6396 Swordsman (