摘要:多任務(wù)系統(tǒng)中有個功能單位任務(wù)進程和線程。操作系統(tǒng)管理其上所有進程的執(zhí)行,并為這些進程合理地分配時間。而子進程還沒執(zhí)行完畢,在后續(xù)執(zhí)行完打印出結(jié)果。對于多線程來說,由于只有一個進程,所以不存在此必要性。主進程等待子進程執(zhí)行結(jié)束才打印。
1、多任務(wù)概念
多任務(wù)處理是指用戶可以在同一時間內(nèi)運行多個應(yīng)用程序,每個應(yīng)用程序被稱作一個任務(wù).Linux就是一個支持多任務(wù)的操作系統(tǒng),比起單任務(wù)系統(tǒng)它的功能增強了許多.
當(dāng)多任務(wù)操作系統(tǒng)使用某種任務(wù)調(diào)度策略允許兩個或更多進程并發(fā)共享一個處理器時,事實上處理器在某一時刻只會給一件任務(wù)提供服務(wù)。因為任務(wù)調(diào)度機制保證不同任務(wù)之間的切換速度十分迅速,因此給人多個任務(wù)同時運行的錯覺。多任務(wù)系統(tǒng)中有3個功能單位:任務(wù)、進程和線程。
當(dāng)操作系統(tǒng)使用某種策略允許兩個或更多進程并發(fā)共享一個CPU時,它稱作多任務(wù)運行,或多道程序運行.在規(guī)定的時間片過期或某些事件發(fā)生前,一直執(zhí)行某個進程.然后,操作系統(tǒng)切換到另一個進程.這種切換十分迅速,給人一種這些進程是同時執(zhí)行的錯覺.而事實上,同一時刻在一個CPU上只能激活一個進程.這種進程間的切換在所有進程完成前一直進行.并發(fā)共享策略決定何時切換進程.該策略由操作系統(tǒng)或其他進程強制執(zhí)行.
2、fork創(chuàng)建子進程2.1.進程的概念
計算機程序只是存儲在磁盤上的可執(zhí)行二進制(或者其他類型)文件。只有把它們加載到內(nèi)存中并被操作系統(tǒng)調(diào)用,才能擁有生命周期。進程則是一個執(zhí)行中的程序。每個進程都擁有自己的地址空間,內(nèi)存,數(shù)據(jù)棧以及其他用于跟蹤執(zhí)行的輔助數(shù)據(jù)。操作系統(tǒng)管理其上所有進程的執(zhí)行,并為這些進程合理地分配時間。進程也可以通過派生(fork或spawn)新的進程來執(zhí)行其他任務(wù),不過因為每個新進程也都擁有自己的內(nèi)存和數(shù)據(jù)棧等,所以只能采用進程間通信(IPC)的方式共享信息。
2.2.fork
Python的os模塊封裝了常見的系統(tǒng)調(diào)用,其中就包括fork,可以在Python程序中輕松創(chuàng)建子進程:
# cat 01-fork.py import os import time #注意,fork函數(shù),只在Unix/Linux/Mac上運行,windows不可以 ret = os.fork() if ret == 0: while True: print("---1---") time.sleep(1) else: while True: print("---2---") time.sleep(1) 運行結(jié)果: # python 01-fork.py ---2--- ---1--- ---2--- ---1--- ---2--- ---1--- ---2--- ---1--- ---2--- ---1--- ...
2.3.fork系統(tǒng)調(diào)用
# cat test.py import os ret = os.fork() print(ret) 執(zhí)行結(jié)果: #python test.py 505 0
調(diào)用一次fork()函數(shù)會有兩個返回值
如果成功創(chuàng)建一個子進程,對于父進程來說返回子進程id
如果成功創(chuàng)建一個子進程,對于子進程來說返回值是0
如果返回值是-1,說明創(chuàng)建失敗
流程圖:
父進程調(diào)用fork()系統(tǒng)調(diào)用,然后陷入內(nèi)核,進行進程復(fù)制,如果成功:
1,則對調(diào)用進程即父進程來說返回值為剛產(chǎn)生的子進程pid,因為進程PCB沒有子進程信息,父進程只能通過這樣獲得。
2,對子進程(剛產(chǎn)生的新進程),則返回0,這時就有兩個進程在接著向下執(zhí)行。如果失敗,則返回0,調(diào)用進程繼續(xù)向下執(zhí)行
注:fork英文意思:分支,fork系統(tǒng)調(diào)用復(fù)制產(chǎn)生的子進程與父進程(調(diào)用進程)基本一樣:代碼段+數(shù)據(jù)段+堆棧段+PCB,當(dāng)前的運行環(huán)境基本一樣,所以子進程在fork之后開始向下執(zhí)行,而不會從頭開始執(zhí)行。
注:參考:http://www.cnblogs.com/mickole/
2.4.getpid和getppid
import os rpid = os.fork() if rpid<0: print("fork調(diào)用失敗。") elif rpid == 0: print("我是子進程(%s),我的父進程是(%s)"%(os.getpid(),os.getppid())) x+=1 else: print("我是父進程(%s),我的子進程是(%s)"%(os.getpid(),rpid)) print("父子進程都可以執(zhí)行這里的代碼") 運行結(jié)果: 我是父進程(19360),我的子進程是(19361) 父子進程都可以執(zhí)行這里的代碼 我是子進程(19361),我的父進程是(19360) 父子進程都可以執(zhí)行這里的代碼
2.5.父進程和子進程的先后順序
父子進程的執(zhí)行順序是不確定的
os.fork()創(chuàng)建出一個子進程后,父進程和子進程分別向下執(zhí)行代碼,父進程并不會因為子進程沒有執(zhí)行完畢而等待子進程,而是退出程序。示例如下:
import os import time ret = os.fork() if ret == 0: time.sleep(5) print("我是子進程") else: print("我是父進程") time.sleep(2) 執(zhí)行結(jié)果: [root@jranson 1-進程編程]# python 03-父子進程執(zhí)行順序.py 我是父進程 [root@jranson 1-進程編程]# 我是子進程 #這里父進程執(zhí)行完畢,退出python程序,返回終端界面。 #而子進程還沒執(zhí)行完畢,在后續(xù)執(zhí)行完打印出結(jié)果。
2.6.全局變量在多個進程之間不共享
import os import time g_num = 100 ret = os.fork() if ret == 0: print("----process-1----") g_num += 1 print("---process-1 g_num=%d---"%g_num) else: time.sleep(3) print("----process-2----") print("---process-2 g_num=%d---"%g_num) 執(zhí)行結(jié)果: ----process-1---- ---process-1 g_num=101--- ----process-2---- ---process-2 g_num=100--- #想要完成進程間的數(shù)據(jù)共享,需要一些方法:命名管道/無名管道/共享內(nèi)存/消息隊列/網(wǎng)絡(luò)等
2.7.多個fork()問題
import os import time ret = os.fork() if ret==0: print("--1--") else: print("--2--") ret = os.fork() if ret==0: print("--11--") else: print("--22--") 執(zhí)行結(jié)果: --2-- --22-- --11-- --1-- --22-- --11--3、multiprocessing模塊
3.1.multiprocessing標準庫
multiprocessing是python中的多進程管理包,它可以利用multiprocessing.Process對象來創(chuàng)建一個進程。該進程可以運行在python程序內(nèi)部編寫的函數(shù)。該Process對象和Thread對象的用法相同,也有start(),run(),jion()方法。此外,multiprocessing包中也有Lock/Event/Semaphore/Condition類 (這些對象可以像多線程那樣,通過參數(shù)傳遞給各個進程),用以同步進程,其用法與threading包中的同名類一致。所以,multiprocessing的很大一部份與threading使用同一套API,只不過換到了多進程的情境。
在使用這些共享API的時候,需要注意幾點:
在UNIX平臺上,當(dāng)某個進程終結(jié)之后,該進程需要被其父進程調(diào)用wait,否則進程成為僵尸進程(Zombie)。所以,有必要對每個Process對象調(diào)用join()方法 (實際上等同于wait)。對于多線程來說,由于只有一個進程,所以不存在此必要性。
multiprocessing提供了threading包中沒有的IPC(比如Pipe和Queue),效率上更高。應(yīng)優(yōu)先考慮Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因為它們占據(jù)的不是用戶進程的資源)。
多進程應(yīng)該避免共享資源。在多線程中,我們可以比較容易地共享資源,比如使用全局變量或者傳遞參數(shù)。在多進程情況下,由于每個進程有自己獨立的內(nèi)存空間,以上方法并不合適。此時我們可以通過共享內(nèi)存和Manager的方法來共享資源。但這樣做提高了程序的復(fù)雜度,并因為同步的需要而降低了程序的效率。
3.2.Process創(chuàng)建子進程
Process的語法結(jié)構(gòu):Process([group [, target [, name [, args [, kwargs]]]]])
target:表示這個進程實例所調(diào)用對象;
args:表示調(diào)用對象的位置參數(shù)元組;
kwargs:表示調(diào)用對象的關(guān)鍵字參數(shù)字典;
name:為當(dāng)前進程實例的別名;
group:大多數(shù)情況下用不到;
Process類常用方法:
is_alive():判斷進程實例是否還在執(zhí)行;
join([timeout]):是否等待進程實例執(zhí)行結(jié)束,或等待多少秒;
start():啟動進程實例(創(chuàng)建子進程);
run():如果沒有給定target參數(shù),對這個對象調(diào)用start()方法時,就將執(zhí)行對象中的run()方法;
terminate():不管任務(wù)是否完成,立即終止;
Process類常用屬性:
name:當(dāng)前進程實例別名,默認為Process-N,N為從1開始遞增的整數(shù);
pid:當(dāng)前進程實例的PID值;
示例:
from multiprocessing import Process #import os from time import sleep # 子進程要執(zhí)行的代碼 def run_pro(name,age,**kwargs): print(name) print(kwargs) sleep(0.5) if __name__ == "__main__": p = Process(target=run_pro,args=("test",18),kwargs={"m":20}) print("子進程將要執(zhí)行") p.start() sleep(1) p.terminate() p.join() print("子進程結(jié)束") 運行結(jié)果: ----------------- 子進程將要執(zhí)行 test {"m": 20} 子進程結(jié)束 ----------------
3.2.Process中主進程等待子進程結(jié)束才結(jié)束
from multiprocessing import Process import time def test(): for i in range(5): print("---test---") time.sleep(1) # 在window中,調(diào)用Process()的時候需要在前面加上if __name__ = "__main__" if __name__ == "__main__": p = Process(target=test) p.start() 執(zhí)行結(jié)果: ---test--- ---test--- ---test--- ---test--- ---test--- Process finished with exit code 0 注意:此處的Process finished with exit code 0是主進程結(jié)束時打印的語句。 主進程等待子進程執(zhí)行結(jié)束才打印。
3.3.Process子類創(chuàng)建子進程
通過Process創(chuàng)建子進程的方式除了直接通過進程類Process進行創(chuàng)建,直接指定target,還可以通過創(chuàng)建繼承Process的子類,并重寫run方法實現(xiàn)子進程的創(chuàng)建。
示例如下:
from multiprocessing import Process import time # 繼承Process class Son(Process): def __init__(self,interval): #這里必須調(diào)用父類的__init__方法 #因為Process類本身也有__init__方法,這個子類相當(dāng)于重寫了這個方法 #我們并沒有完全初始化一個Process類,最好的方法就是講繼承類本身傳遞給 #Process.__init__方法,完成這些初始化操作 Process.__init__(self) self.interval = interval #重寫run方法 def run(self): print("子進程開始運行...") start_time = time.time() time.sleep(self.interval) end_time = time.time() print("子進程執(zhí)行結(jié)束,耗時%0.2f秒"%(end_time-start_time)) if __name__ == "__main__": #開辟一個新的進程實際上就是執(zhí)行本進程所對應(yīng)的run()方法 p1 = Son(2) p1.start() p1.join() print("我是主進程") 運行結(jié)果: ---------- 子進程開始運行... 子進程執(zhí)行結(jié)束,耗時2.00秒 我是主進程4、進程池
當(dāng)需要創(chuàng)建的子進程數(shù)量不多時,可以直接利用multiprocessing中的Process動態(tài)成生多個進程,但如果是上百甚至上千個目標,手動的去創(chuàng)建進程的工作量巨大,此時就可以用到multiprocessing模塊提供的Pool方法。
初始化Pool時,可以指定一個最大進程數(shù),當(dāng)有新的請求提交到Pool中時,如果池還沒有滿,那么就會創(chuàng)建一個新的進程用來執(zhí)行該請求;但如果池中的進程數(shù)已經(jīng)達到指定的最大值,那么該請求就會等待,直到池中有進程結(jié)束,才會創(chuàng)建新的進程來執(zhí)行。
from multiprocessing import Pool import os,time,random def worker(msg): t_start = time.time() print("%s開始執(zhí)行,進程號為%d"%(msg,os.getpid())) #random.random()隨機生成0~1之間的浮點數(shù) time.sleep(random.random()*2) t_stop = time.time() print(msg,"執(zhí)行完畢,耗時%0.2f"%(t_stop-t_start)) po=Pool(3) #定義一個進程池,最大進程數(shù)3 for i in range(0,10): #Pool.apply_async(要調(diào)用的目標,(傳遞給目標的參數(shù)元祖,)) #每次循環(huán)將會用空閑出來的子進程去調(diào)用目標 po.apply_async(worker,(i,)) print("----start----") po.close() #關(guān)閉進程池,關(guān)閉后po不再接收新的請求 po.join() #等待po中所有子進程執(zhí)行完成,必須放在close語句之后 print("-----end-----") 運行結(jié)果: ----start---- 0開始執(zhí)行,進程號為21466 1開始執(zhí)行,進程號為21468 2開始執(zhí)行,進程號為21467 0 執(zhí)行完畢,耗時1.01 3開始執(zhí)行,進程號為21466 2 執(zhí)行完畢,耗時1.24 4開始執(zhí)行,進程號為21467 3 執(zhí)行完畢,耗時0.56 5開始執(zhí)行,進程號為21466 1 執(zhí)行完畢,耗時1.68 6開始執(zhí)行,進程號為21468 4 執(zhí)行完畢,耗時0.67 7開始執(zhí)行,進程號為21467 5 執(zhí)行完畢,耗時0.83 8開始執(zhí)行,進程號為21466 6 執(zhí)行完畢,耗時0.75 9開始執(zhí)行,進程號為21468 7 執(zhí)行完畢,耗時1.03 8 執(zhí)行完畢,耗時1.05 9 執(zhí)行完畢,耗時1.69 -----end-----
multiprocessing.Pool常用函數(shù)解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式調(diào)用func(并行執(zhí)行,堵塞方式必須等待上一個進程退出才能執(zhí)行下一個進程),args為傳遞給func的參數(shù)列表,kwds為傳遞給func的關(guān)鍵字參數(shù)列表;
apply(func[, args[, kwds]]):使用阻塞方式調(diào)用func
close():關(guān)閉Pool,使其不再接受新的任務(wù);
terminate():不管任務(wù)是否完成,立即終止;
join():主進程阻塞,等待子進程的退出, 必須在close或terminate之后使用;
5、進程間通信文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42872.html
摘要:鋪墊已了,進入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進階和提高的讀者閱讀。書中列舉了兩個慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級引入的錯誤,損失億美元公司小時全球中斷的嚴重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語言的使用趨勢的人都知道,最近幾年,國內(nèi)最火的兩種語言非...
摘要:鋪墊已了,進入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進階和提高的讀者閱讀。書中列舉了兩個慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級引入的錯誤,損失億美元公司小時全球中斷的嚴重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語言的使用趨勢的人都知道,最近幾年,國內(nèi)最火的兩種語言非...
閱讀 1980·2021-11-24 10:45
閱讀 1468·2021-11-18 13:15
閱讀 4562·2021-09-22 15:47
閱讀 3941·2021-09-09 11:36
閱讀 2018·2019-08-30 15:44
閱讀 3097·2019-08-29 13:05
閱讀 2510·2019-08-29 12:54
閱讀 2003·2019-08-26 13:47