




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
第14章
利用Python進行多任務(wù)編程
目錄2進程和線程Python中的多線程編程Python中的進程編程14.114.214.314.1進程和線程簡介在計算機科學(xué)的背景下,多任務(wù)通常是指可以在同一時刻運行多個應(yīng)用程序,而這里的每個應(yīng)用程序被稱為一個任務(wù)。多任務(wù)一方面是操作系統(tǒng)管理同時運行的不同程序的一種方式,另一方面也可以被用來最大程度地利用硬件計算資源(例如,N核處理器中,每個核心都可以完成一個任務(wù),并且同時進行,容易知道在極其理想的情況下計算效率可以提升N倍)。后者在當(dāng)下大數(shù)據(jù)處理和分析需求以及硬件計算能力快速提升的雙重背景下顯得尤為重要。讀者可能會經(jīng)常聽到并行處理、分布式計算等概念,以及流行的MapReduce、CUDA等并行計算平臺,這些概念均是多任務(wù)協(xié)同計算的嘗試。在本節(jié)中,將首先幫助讀者熟悉多任務(wù)編程中的兩個重要概念—進程和線程。由于深入剖析進程和線程的內(nèi)部機制需要諸多先修知識,下面僅盡可能通俗而簡要地從一個程序開發(fā)者的角度介紹這兩個概念,詳細知識可參閱相關(guān)教材。14.1.1進程通俗地說,一個程序的一次獨立運行是一個進程,進程是操作系統(tǒng)資源管理的最小單位。操作系統(tǒng)會隔離每個進程,使程序員以及用戶可以認(rèn)為每個任務(wù)都是在獨占系統(tǒng)資源,包括內(nèi)存、處理器等。然而,從實現(xiàn)上來看,處理器上只可以同時運行有限多的進程。于是,操作系統(tǒng)采用進程調(diào)度的方式,多個進程需要排隊利用處理器的運算資源。當(dāng)進程得到處理器資源后,很短時間內(nèi)就會主動或被動交出處理器,讓給下一個進程使用,而自己繼續(xù)排隊等待下一次使用機會,這一過程被稱為進程切換。由于輪轉(zhuǎn)時間很短,故從宏觀的視角看來就像是每個程序在連續(xù)不斷地運行著。需要注意的是,這種進程級別的輪轉(zhuǎn)調(diào)度需要付出較大時間代價,即進程切換所需要的時間是比較長的。另一方面,這種獨占性也意味著進程之間無法直接共享數(shù)據(jù),而需要使用進程間通信,或者其他基于前面章節(jié)所介紹的文件系統(tǒng)或數(shù)據(jù)庫系統(tǒng)的方法。14.1.2線程一個進程是程序執(zhí)行的最小單位,每個處理器的核心上都可以運行一個線程。通常,一個進程中可能包含一個主線程。但為了提高效率,有些程序會使用多線程技術(shù),這時一個進程中就會包含多個線程。例如,在一個手機聽歌軟件中,可能有一個主線程負(fù)責(zé)界面的刷新,有一個線程負(fù)責(zé)從互聯(lián)網(wǎng)上下載并緩存歌曲。在這個場景下多線程還是很有意義的,因為如果界面刷新和網(wǎng)絡(luò)連接在同一線程中,很有可能界面的刷新操作會因網(wǎng)絡(luò)的延遲而等待,從而造成界面卡頓,會極大地影響用戶體驗;在下一小節(jié)中明確了串行和并行的概念之后,讀者將會對此有更深的理解。與進程一樣,線程的數(shù)目也是遠多于計算資源(處理器的核數(shù))的,操作系統(tǒng)會使用類似的調(diào)度策略。然而,由于同一進程的多個線程之間共享內(nèi)存空間,線程間的通信會十分容易(甚至于不會被發(fā)覺)地實現(xiàn);同一進程內(nèi)線程間的切換代價要遠小于進程的切換代價,因為在調(diào)度過程中只有少量的線程所獨有的數(shù)據(jù)需要切換。因此,線程有時也被稱為“輕量級進程”。14.1.3串行,并發(fā)與并行了解串行、并發(fā)和并行的概念是理解多任務(wù)編程的基礎(chǔ)。串行是指程序中的每行指令按照其順序被計算機執(zhí)行,如之前章節(jié)中的程序均是串行執(zhí)行的。并發(fā),顧名思義,則是指同時運行兩個程序,而這兩個程序是并列關(guān)系,沒有邏輯上的先后關(guān)系。并行,則是指并發(fā)的多個程序在同一時刻可以同時執(zhí)行其各自的指令。例如,有以下兩個程序funcA和funcB:deffuncA():foriin['A','B','C']:print(i,end='')deffuncB():foriin[1,2,3]:print(i,end='')當(dāng)調(diào)用函數(shù)funcA時,程序會依次輸出'A'、'B'、'C'三個字符,這個順序是在函數(shù)中預(yù)先指定好的(即字符在list中出現(xiàn)的順序),而函數(shù)的每次執(zhí)行都會按照預(yù)先定義順序地執(zhí)行下去,這就是串行的概念。14.1.3串行,并發(fā)與并行同樣,可以用如下的代碼依次調(diào)用如下兩個函數(shù)。funcA();funcB()程序?qū)⑤敵觯篈BC123。從執(zhí)行結(jié)果中可以看出,函數(shù)funcB總是會在函數(shù)funcA執(zhí)行完后再執(zhí)行,也就是說,3個數(shù)字的輸出永遠在3個字符的輸出之后。下面來考慮一種并發(fā)的機制,funcA和funcB作為兩個并列的函數(shù)會同時運行。在這種情況下,數(shù)字和字符的輸出并沒有預(yù)定的先后關(guān)系:函數(shù)funcB可以在funcA之前運行,產(chǎn)生"123ABC"的輸出;當(dāng)然,更多的時候是兩個函數(shù)交替運行,而運行結(jié)果交織在一起,"1A2B3C"、"12ABC3"、"1AB23C"等都是可能的輸出結(jié)果。14.1.3串行,并發(fā)與并行稍加觀察就可以發(fā)現(xiàn),雖然字母和數(shù)字之間有多種輸出的排列方式,但是函數(shù)funcA和函數(shù)funcB內(nèi)部的語句仍然是順序執(zhí)行的,即字母總是按字母表順序輸出的,數(shù)字總是按從小到大的順序輸出的。圖14-1形象地描述了并發(fā)和串行的概念及其關(guān)系。14.1.3串行,并發(fā)與并行在這個例子中,由于函數(shù)funcA和函數(shù)funcB需要將字符打印到同一個控制臺上,所以無法在同一時刻實現(xiàn)兩個函數(shù)真正地并行運行。但是,如果假設(shè)這兩個函數(shù)之間不相互“影響”,那么在多核的處理器上,這兩個函數(shù)是可以并行執(zhí)行的,圖14-2可以幫助讀者理解并發(fā)和并行這個兩個概念的異同。在本章中,將從線程和進程兩個級別上介紹Python中這種多任務(wù)并發(fā)和并行的編程實現(xiàn)。14.2Python中多線程編程簡介在本節(jié)中,將首先介紹如何創(chuàng)建和管理線程,然后介紹多任務(wù)編程中最重要的一個問題—“同步”的兩種解決方案,即鎖機制和使用Queue模塊構(gòu)造線程隊列。14.2.1線程的創(chuàng)建和管理Python中提供了兩個多線程模塊:_thread模塊和threading模塊。其中,前者是一個較為簡單且低層的多線程實現(xiàn),僅提供原始的線程及互斥鎖創(chuàng)建方法;而后者是對thread模塊的擴展,使用threading模塊可以進行更全面的線程管理。本節(jié)將首先簡單地介紹_thread模塊中的多線程創(chuàng)建方式,之后將重點幫助讀者了解threading模塊的使用。
使用_thread模塊創(chuàng)建線程_thread模塊提供了十分簡單的線程創(chuàng)建方法,只需要調(diào)用如下函數(shù)即可創(chuàng)建一個線程。_thread.start_new_thread(function,args[,kwargs])其中,第一個輸入?yún)?shù)function是一個函數(shù),該函數(shù)包含的是要在新創(chuàng)建的線程中執(zhí)行的代碼,該函數(shù)執(zhí)行完畢后,線程也將終止;args是一個元組,需要傳入函數(shù)的參數(shù),若沒有需要傳入的參數(shù),則需使用空元組;kwargs是一個可選的參數(shù)字典,以字典的方式指定傳入函數(shù)function的參數(shù)。有了這個函數(shù),編程者就可以實現(xiàn)函數(shù)funcA和funcB的并發(fā)執(zhí)行功能了,這里用向func傳入不同的參數(shù)的方法來分別實現(xiàn)funcA和funcB的功能importsys
import_thread
importtime
deffunc(output_list):
foriinoutput_list:
print(i,end='')
time.sleep(0.1)#為了使交錯執(zhí)行的效果更明顯
defrunFunc():
_thread.start_new_thread(func,(['A','B','C'],))#完成funcA的功能
_thread.start_new_thread(func,([1,2,3],))#完成funcB的功能
time.sleep(1)#等待兩個線程執(zhí)行完畢
sys.stdout.flush()#刷新標(biāo)準(zhǔn)輸出緩沖區(qū),以顯示完整運行結(jié)果
foriinrange(5):#觀察5次運行結(jié)果
print("#%d:"%i,end='')
runFunc()
print()
使用_thread模塊創(chuàng)建線程可能讀者會注意到,為了保證完整地顯示輸出結(jié)果,在runFunc后有一行休眠語句,用于等待兩個線程執(zhí)行完畢后刷新緩沖區(qū)再返回主程序。這種實現(xiàn)方式是十分笨拙且低效的(通常會因為無法預(yù)估線程的執(zhí)行時間而設(shè)置一個盡可能大的等待時間),在下面對threading模塊的介紹中,可以通過更高層且更便捷的方式實現(xiàn)這種“等待線程終止”及其他相關(guān)的線程管理功能。
使用threading模塊創(chuàng)建和管理線程使用threading模塊創(chuàng)建一個線程比使用thread模塊略微復(fù)雜一些,需要進行以下三步。(1)定義threading.Thread的一個子類。(2)重寫該子類的初始化函數(shù)__init__(self[,args]),指明在新線程執(zhí)行前需要完成的工作。(3)重寫該子類的run(self,[,args])函數(shù),實現(xiàn)希望該線程在開始執(zhí)行時要完成的功能。除了上述兩個需要自定義的函數(shù)之外,threading.Thread類還提供了以下函數(shù)用以管理創(chuàng)建的進程。(1)start():調(diào)用該函數(shù)將開始一個線程的執(zhí)行。注意,一個線程只可以被執(zhí)行一次,第二次調(diào)用同一個線程的start()函數(shù)將得到異常。(2)join():該函數(shù)用來等待線程的終止。(3)is_alive():該函數(shù)用于測試線程是否還在執(zhí)行(指run函數(shù)從開始執(zhí)行后直到終止前的狀態(tài))。
使用threading模塊創(chuàng)建和管理線程這里使用join函數(shù)的調(diào)用替代了原來runFunc中通過睡眠(sleep)等待線程終止的方式。這樣做不需要提前預(yù)估線程的執(zhí)行時間,可使程序更加健壯而高效,且增加了代碼的可讀性。同時,threading模塊還提供了下列靜態(tài)函數(shù)以方便管理全局所有的線程。(1)threading.active_count():該函數(shù)返回當(dāng)前正在執(zhí)行的線程數(shù)目。(2)threading.enumerate():該函數(shù)返回包含所有正在執(zhí)行線程的列表。(3)threading.current_thread():返回當(dāng)前的線程對象。importthreading
importtime
importsys
classmyThread(threading.Thread):
def__init__(self,output_list):
threading.Thread.__init__(self)
self.output_list=output_list#初始化輸出列表
defrun(self):
foriinself.output_list:
print(i,end='')
time.sleep(0.1)#使交錯執(zhí)行的效果更佳明顯
defrunFunc():
#創(chuàng)建線程
thread1=myThread(['A','B','C'])
thread2=myThread([1,2,3])
#開始線程的執(zhí)行
thread1.start()
thread2.start()
#等待線程的終止
thread1.join()
thread2.join()
sys.stdout.flush()#刷新標(biāo)準(zhǔn)輸出緩沖區(qū)
臨界區(qū)和臨界資源如前面描述的,同一進程內(nèi)的多個線程共享數(shù)據(jù),然而,這在方便了線程間通信的同時也帶來了一個并發(fā)訪問共享資源時的沖突問題—線程同步。線程同步中最主要的問題在于程序?qū)εR界資源與臨界區(qū)的合理協(xié)調(diào)管控,其中,臨界資源是指同一時間只允許一個線程訪問的資源,而臨界區(qū)是指同一時間只允許一個線程執(zhí)行的一部分代碼區(qū)域,通常這個區(qū)域內(nèi)會包含對臨界資源的共享使用。例如,前面例程中的控制臺就可以看作是一個臨界資源,同一時間內(nèi)不可以有兩個線程同時向控制臺緩沖區(qū)寫入內(nèi)容。但這一臨界資源是由系統(tǒng)處理的,我們在代碼中還會遇到一些自定義的臨界資源,共享變量便是其中一種,我們需要在程序中謹(jǐn)慎小心地處理這些臨界資源,否則程序結(jié)果將與預(yù)期產(chǎn)生很大偏差。
臨界區(qū)和臨界資源觀察程序輸出結(jié)果,可以發(fā)現(xiàn)在500個線程執(zhí)行之后,全局變量count的值不是500而是481(這一數(shù)字可能會因每次運行而不同)。這是因為對count的增一操作實際上并不是由一條底層機器指令完成的(術(shù)語稱為“不是原子操作”),讀者可以理解為該條語句被分解為以下3條語句。tmp<-count #首先,將內(nèi)存中count的值取到一個加法寄存器tmp中tmp<-tmp+1 #將tmp增1count<-tmp #將增1后的tmp寫回至內(nèi)存中的count變量上當(dāng)有多個線程同時執(zhí)行count的增一操作時,會出現(xiàn)兩個線程同時取出count的值到兩個不同的tmp寄存器中,然后分別增1后寫回的情況,這樣兩個線程的運行只會造成count增加1而非2;這也不是唯一造成結(jié)果的原因,甚至?xí)l(fā)生以下情況:一個線程取到count中的值后因為排隊沒有及時將增1后的tmp值寫回,直到若干個進程從開始到執(zhí)行完畢后才將tmp值寫回,這樣導(dǎo)致這些進程對count的增加都變成無效的操作。這里,全局共享的count變量就是臨界資源,而count的增1指令就是臨界區(qū)內(nèi)的語句。可見,合理的控制對共享臨界資源的訪問和臨界區(qū)代碼的執(zhí)行是非常重要的,下面將介紹如何用鎖機制來解決線程同步的問題。importthreading,sys,time
count=0
classmyThread(threading.Thread):
defrun(self):
globalcount
time.sleep(0.1)
threadLock.acquire()#獲得鎖
count+=1#臨界區(qū)代碼
threadLock.release()#釋放鎖
threadLock=threading.Lock()#建立鎖對象
foriinrange(500):
thread=myThread()
thread.start()
foriinrange(500):
thread.join()
print(count)
互斥鎖鎖機制是處理同步問題的常用方法,其思想是保證臨界區(qū)只有一個線程可以進入,一旦進入則該臨界區(qū)域(即開始執(zhí)行臨界區(qū)域內(nèi)的代碼)就被“鎖上”,直到該線程釋放這個鎖,其他線程才可進入此臨界區(qū)域。在Python中,threading模塊提供了Lock互斥鎖類,該對象的acquire()方法可以實現(xiàn)對臨界區(qū)域的“鎖定”,release()方法可以實現(xiàn)對臨界區(qū)域的“解鎖”。下面將鎖機制加入到前面的計數(shù)器例子中,以實現(xiàn)計數(shù)器對進程數(shù)的準(zhǔn)確統(tǒng)計,如代碼清單14-4所示。
互斥鎖可以看到,此時計數(shù)器工作正常,輸出結(jié)果和預(yù)期一致。當(dāng)一個線程在執(zhí)行count增1操作前會先調(diào)用acquire方法請求獲得鎖將臨界區(qū)“鎖上”,而如果之前有線程已經(jīng)獲得了鎖,即已經(jīng)將臨界區(qū)“鎖定”,那么這個請求將被放入一個等待隊列,直到獲得了鎖的線程執(zhí)行完畢臨界區(qū)中的代碼,調(diào)用release方法將鎖釋放,才可以批準(zhǔn)等待隊列中的一個線程的鎖請求。這樣,可以保證一次只有一個線程對共享變量count進行訪問,完成對其的讀寫。importthreading,sys,time
count=0
classmyThread(threading.Thread):
defrun(self):
globalcount
time.sleep(0.1)
threadLock.acquire()#獲得鎖
count+=1#臨界區(qū)代碼
threadLock.release()#釋放鎖
threadLock=threading.Lock()#建立鎖對象
foriinrange(500):
thread=myThread()
thread.start()
foriinrange(500):
thread.join()
print(count)
互斥鎖鎖機制可能會帶來一些新的問題。其中最常見的問題就是死鎖,即兩個或兩個以上的線程在執(zhí)行過程中因爭奪資源而導(dǎo)致的互相等待的現(xiàn)象。importthreading,sys,time
count=0
classmyThread(threading.Thread):
defrun(self):
globalcount
time.sleep(0.1)
threadLock.acquire()#獲得鎖
count+=1#臨界區(qū)代碼
threadLock.release()#釋放鎖
threadLock=threading.Lock()#建立鎖對象
foriinrange(500):
thread=myThread()
thread.start()
foriinrange(500):
thread.join()
print(count)14.2.3queue模塊:隊列同步雖然使用threading模塊中提供的功能可以完成大多數(shù)線程同步的需求,然而依靠互斥鎖等機制實現(xiàn)線程同步是十分復(fù)雜的,且對于初學(xué)者來說稍不注意就會導(dǎo)致死鎖等問題的產(chǎn)生。但幸運的是,Python中實現(xiàn)了可以支持多線程共享的隊列模塊—queue,用戶可以簡單地使用其提供的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)線程同步。下面先簡單地介紹一下該模塊。queue模塊中提供了3種數(shù)據(jù)結(jié)構(gòu):先入先出隊列Queue類,后入先出“隊列”(其實它是棧數(shù)據(jù)結(jié)構(gòu),而非隊列)LifoQueue類和按優(yōu)先級高低決定出隊順序的優(yōu)先級隊列PriorityQueue類。這3種數(shù)據(jù)結(jié)構(gòu)都實現(xiàn)了鎖原語,能夠在多線程中直接使用,即當(dāng)多個線程同時執(zhí)行這些數(shù)據(jù)結(jié)構(gòu)的入隊、出隊等操作時,它們都會自動保證多個線程不發(fā)生沖突。queue模塊的Queue類有以下幾個常用方法,其他兩個類與此類似。14.2.3queue模塊:隊列同步(1)Queue.get():從隊列中獲取一個元素,并將其從隊列中刪除(出隊)。(2)Queue.put(item):將item添加到隊列中(入隊)。(3)Queue.qsize():返回隊列的大小。(4)Queue.empty():判斷隊列是否為空,若空則返回True,反之返回False。(5)Queue.full():如果隊列已滿(即隊列大小等于由對象創(chuàng)建時指定的maxsize,若創(chuàng)建時沒有給出或小于等于0,則認(rèn)為是無限長的隊列),則返回True,反之返回False。(6)Queue.join():阻塞調(diào)用線程,直到隊列中的所有任務(wù)被處理掉。(7)Queue.task_done():在完成隊列中的某項工作之后,需用該函數(shù)向隊列發(fā)送一個信號,以幫助因Queue.join()阻塞的線程判斷隊列中的任務(wù)已全部被完成,從而不必阻塞地繼續(xù)運行下去。14.2.3queue模塊:隊列同步在上述代碼中,創(chuàng)建了3個爬蟲線程ThreadUrl并發(fā)完成隊列Queue中指定URL的網(wǎng)頁爬取任務(wù),并使用隊列Queue的join方法等待由其指定的URL爬取任務(wù)的完成。在每個線程中,首先使用get方法從任務(wù)隊列Queue中得到一個URL,隨后爬取該URL對應(yīng)的網(wǎng)頁內(nèi)容并輸出,最后使用task_done方法通知被join方法所阻塞的主線程,以判斷Queue隊列的任務(wù)是否已全部完成,也就是說,當(dāng)最后一個線程完成任務(wù)并調(diào)用task_done方法(也是第5次調(diào)用task_done)后,所有任務(wù)完成,主線程接觸阻塞,繼續(xù)執(zhí)行下面的代碼,輸出“Done!”。注意,在進程運行前已經(jīng)使用setDaemon方法將進程設(shè)置為守護線程,使得主線程在結(jié)束時會終結(jié)所有守護線程。(Python中主線程會等待所有非守護線程終止后再終止。)這種方式創(chuàng)建了一種簡單的方式以控制程序流程,因為只需對隊列執(zhí)行join操作后即可退出主線程,而不用手動終結(jié)所有線程。在類似的程序中,還可以根據(jù)不同任務(wù)的重要程度,使用優(yōu)先隊列PriorityQueue實現(xiàn)重要任務(wù)先分發(fā)給線程,而不太重要的任務(wù)后分發(fā)給線程的功能。為了指定優(yōu)先級,可以使用PriorityQueue.put((priority,item))的方式插入元素item并給予其優(yōu)先級priority(值越小,表明優(yōu)先級越高)。importqueue,\
threading,urllib.request
hosts=["","",
"",""]#待爬取的URL列表
Queue=queue.Queue()
classThreadUrl(threading.Thread):
def__init__(self,queue):
threading.Thread.__init__(self)
self.queue=queue
defrun(self):
whileTrue:
#從任務(wù)隊列中取出一個URL
host=self.queue.get()
#爬取頁面內(nèi)容
url=urllib.request.urlopen(host)
print(url.read(1024).decode("utf-8"))
#發(fā)出有一項任務(wù)已完成的信號
self.queue.task_done()
#建立爬蟲線程
foriinrange(3):
t=ThreadUrl(Queue)
t.setDaemon(True)#將進程設(shè)置為守護進程
t.start()
#將需要爬取的URL加入隊列
forhostinhosts:
Queue.put(host)
#等待所有線程任務(wù)完成
Queue.join()
print("Done!")14.3Python中的進程編程14.3.1進程的創(chuàng)建和終止Python在os模塊中提供了兩種進程創(chuàng)建方式:system函數(shù)和exec家族函數(shù)。它們各有異同,分別適用于不同的進程創(chuàng)建需求。在介紹完線程創(chuàng)建后,接下來介紹Python中終止進程的方法。當(dāng)掌握進程的創(chuàng)建和終止后,將帶領(lǐng)讀者編寫一個簡易的控制臺(類似于UNIXShell或者Windows中的命令行)。
使用system函數(shù)創(chuàng)建進程os模塊的system函數(shù)是創(chuàng)建新進程最為簡單的方式,其語法如下。status=system(command)其中,command是新創(chuàng)建的進程將要執(zhí)行的字符串命令,status是表示新進程是否正確執(zhí)行的返回值,若返回值status為0,則通常表示進程創(chuàng)建運行成功。利用該函數(shù)可以幫助用戶執(zhí)行系統(tǒng)命令,例如,下面的代碼可以創(chuàng)建一個新進程執(zhí)行l(wèi)s指令(Linux命令),輸出當(dāng)前文件夾下的文件列表,這和在命令行中輸入ls是同樣的效果。ifos.system("ls")==0:print("以上是當(dāng)前文件夾下的文件列表.")
使用exec函數(shù)和fork函數(shù)創(chuàng)建子進程exec家族包含8個類似的函數(shù),它們的參數(shù)輸入各有差別,但它們共同的特性是可以執(zhí)行新的程序替代原來的Python進程,也就是說,這個函數(shù)執(zhí)行后,原來的Python進程將不再存在,所以這個函數(shù)永遠不會再返回。下面列出了8個exec家族函數(shù)的原型。os.execl(path,arg0,arg1,)os.execle(path,arg0,arg1,,env)os.execlp(file,arg0,arg1,)os.execlpe(file,arg0,arg1,,env)os.execv(path,args)os.execve(path,args,env)os.execvp(file,args)os.execvpe(file,args,env)其中,path用于指定新執(zhí)行程序的路徑,file表示要執(zhí)行的程序(在函數(shù)名沒有p的函數(shù)中,會在系統(tǒng)環(huán)境變量PATH中定位file程序;否則,需使用path指明完整路徑),args表示程序的輸入?yún)?shù),env可用字典方式設(shè)置新進程執(zhí)行時的環(huán)境變量。
使用exec函數(shù)和fork函數(shù)創(chuàng)建子進程一個簡單的例子是,當(dāng)執(zhí)行完自己的程序后,需要轉(zhuǎn)入另外一個程序的執(zhí)行(可以是任意可執(zhí)行程序,不必是Python程序,假設(shè)為“a.out”,則調(diào)用參數(shù)為“-a”)。此時,exec家族的函數(shù)即可幫助用戶完成這項任務(wù),即創(chuàng)建一個新進程替代自己原來的Python程序。示例代碼如下,importos#完成一些任務(wù)(代碼略去)os.execl('./a.out','-a')
#執(zhí)行a.out,接替原Python進程但是,像讀者看到的,exec家族函數(shù)只是起到了替代原有進程的作用,通常在實際使用中,該函數(shù)是和os模塊的fork函數(shù)配合使用以達到新進程創(chuàng)建功能的。
使用exec函數(shù)和fork函數(shù)創(chuàng)建子進程fork函數(shù)的功能是創(chuàng)建一個新的子進程,與通常的函數(shù)不同,fork函數(shù)的一次執(zhí)行會有兩次返回,一次是在主進程中(返回子進程號),一次是在子進程中(返回0)。通常而言,一個子進程創(chuàng)建框架如下:pid=os.fork()ifpid==0:
實現(xiàn)子進程完成的功能,例如,用exec家族函數(shù)執(zhí)行新程序 execl('./a.out','-a)else:
執(zhí)行主進程接下來的任務(wù)這種由fork函數(shù)和exec函數(shù)配合使用的進程創(chuàng)建方式與system函數(shù)提供的進程創(chuàng)建方式是不同的,前者創(chuàng)建的子進程會與主進程并發(fā)執(zhí)行,而后者只能等system函數(shù)創(chuàng)建的新進程執(zhí)行完畢并返回后,主進程才可繼續(xù)執(zhí)行。
使用sys.exit函數(shù)終止進程exit函數(shù)是常規(guī)的進程終止方式,在進程終止前,會執(zhí)行一些清理工作,同時將返回值返回給調(diào)用進程(如os.system的返回值)。使用該返回值可以判斷程序是正確退出還是因異常而終止的。其函數(shù)調(diào)用語法如下:sys.exit(exit_code)其中,由exit_code指定返回給調(diào)用進程的返回值。同時,該函數(shù)的調(diào)用意味著自身進程的終結(jié),所以和exec家族函數(shù)一樣,該函數(shù)調(diào)用也不會有返回值。14.3.2實例:編寫簡易的控制臺在cmd.py程序中,首先主進程不斷讀取命令,若命令為空,則提示用戶輸入要執(zhí)行的命令;若命令為exit,則調(diào)用exit函數(shù)退出該程序。當(dāng)讀取到一條正常的指令時,程序會調(diào)用system函數(shù)創(chuàng)建新進程完成該指令,并利用其返回值判斷該指令執(zhí)行的成功與否。當(dāng)然,該程序的實現(xiàn)比較簡單,其實命令的解析和執(zhí)行工作仍是由system函數(shù)交給系統(tǒng)的控制臺程序執(zhí)行的。有興趣的讀者可以考慮嘗試使用fork函數(shù)和exec家族函數(shù)配合的方式實現(xiàn)一個自帶命令解析和執(zhí)行功能的控制臺程序。14.3.3使用subprocess進行多進程管理前面介紹的進程創(chuàng)建和終止是較為底層的實現(xiàn)方式,使用它們進行進程的創(chuàng)建和管理將會十分復(fù)雜。Python在2.4版本之后引入了subprocess模塊,提供了較為高級的進程管理功能。在subprocess模塊中,多進程的管理功能主要源于Popen類的靈活使用,接下來將介紹這個類的使用方法。可以通過調(diào)用subprocess.Popen()函數(shù)來創(chuàng)建一個Popen類的對象,一個Popen對象對應(yīng)于一個新的子進程,而用戶可通過對該對象的操作實現(xiàn)對進程的管理。例如,可以用下面的代碼創(chuàng)建一個運行ping命令的子進程。14.3.3使用subprocess進行多進程管理importsubprocess#下面的代碼中,-c4表示發(fā)送四次ping報文(Windows中使用-n4)child=subprocess.Popen('ping–c4',shell=True)。Popen對象的常用屬性主要有以下幾個。(1)pid:子進程的進程號。(2)returncode:子進程的返回值。如果進程還沒有結(jié)束,則返回None。(3)stdin:子進程的標(biāo)準(zhǔn)輸入流對象。(4)stdout:子進程的標(biāo)準(zhǔn)輸出流對象。(5)stderr:子進程的標(biāo)準(zhǔn)日志流對象。其中,后三個屬性可以在Popen的構(gòu)造函數(shù)中設(shè)置。Popen對象的常用成員函數(shù)主要有以下幾個。(1)wait():等待子進程結(jié)束,設(shè)置并返回returncode屬性。例如,上面的例子調(diào)用child.wait()后將等待子進程發(fā)送4次ping報文并完成結(jié)果統(tǒng)計。(2)poll():用于檢查子進程是否已經(jīng)結(jié)束,設(shè)置并返回returncode屬性。(3)kll():殺死子進程。(4)terminate():停止子進程(與kill方法在實現(xiàn)上略有差別)。(5)send_signal(signal):向子進程發(fā)送信號。(6)communicate(input=None):與子進程進行交互。14.3.4進程間通信進程之間的數(shù)據(jù)獨立性使得進程通信成為一個重要問題,本小節(jié)將主要介紹兩種簡單的進程通信方式:信號和管道
使用信號進行進程通信信號處理是進程間通信的一種方式。信號是操作系統(tǒng)提供的一種軟件中斷,是一種異步的通信方式。例如,控制臺用戶按下中斷鍵(Ctrl+C),操作系統(tǒng)就會生成一個中斷信號(SIGINT)并發(fā)送給當(dāng)前運行的程序;應(yīng)用程序會檢查是否有信號傳來,當(dāng)發(fā)現(xiàn)了中斷信號后會調(diào)用該信號對應(yīng)的信號處理程序終結(jié)該進程,完成系統(tǒng)向該進程的通信過程。當(dāng)然,信號并不只局限于中斷信號,還包括很多,用戶甚至可以自己定義。更重要的是,針對每個信號,不同的程序可以設(shè)置不同的自定義信號處理程序(除少數(shù)幾個系統(tǒng)不允許自定義處理的信號之外)。在Python中,可以使用signal模塊中的signal函數(shù)定義進程針對不同信號自定義的處理程序。例如,可以重定義當(dāng)前進程對中斷信號(SIGINT)的處理。sigint_handler函數(shù)(原型為sigint_handler(signum,frame))展示了信號處理程序的函數(shù)原型,其中,參數(shù)signum是信號,而frame是進程棧的狀況。需要說明的是,signal模塊中提供了系統(tǒng)支持的多種信號(如上面的signal.SIGINT),這些信號其實只是數(shù)值,但使用該方式可以提高代碼的跨平臺可移植性,也增加了代碼的可讀性。如前面介紹的,使用Popen對象的send_signal函數(shù)可以向子進程發(fā)送信號。事實上,os模塊中也提供了kill(pid,signal)函數(shù),可以向任意進程發(fā)送信號,其中pid為接收信號方的進程號,signal為要發(fā)送的信號。代碼清單14-9將展示如何實現(xiàn)主進程與計數(shù)子進程間的通信。
使用信號進行進程通信在上面的代碼中,將用戶自定義信號SIGUSR1(其實也可以用一個沒有被其他信號占用的數(shù)值代替)定義為計數(shù)器的加1信號,SIGUSR2定義為計數(shù)器報數(shù)信號,并使用signal函數(shù)制定了相應(yīng)的處理函數(shù)。該計數(shù)器將一直在while死循環(huán)中等待信號的到來,直到收到SIGINT信號時終止。importsignal,sys
count=0
#SIGUSR1處理程序
defadd(signum,frame):
globalcount
count+=1
print("計數(shù)器加1.")
#SIGUSR2處理程序
defshow(signum,frame):
print("計數(shù)器當(dāng)前值為%d."%count)
#SIGINT處理程序
defsigint_handler(signum,frame):
print("謝謝使用!
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 公司疫情防護管理制度
- 公司行政后期管理制度
- 公司設(shè)計崗位管理制度
- 河南省平頂山市2024~2025學(xué)年 高三下冊開學(xué)摸底考試數(shù)學(xué)試卷附解析
- 廣東省中山市2024~2025學(xué)年 高二下冊第二次統(tǒng)測(4月)數(shù)學(xué)試卷附解析
- 廣東省部分學(xué)校2025屆高三年級5月月考數(shù)學(xué)試卷附解析
- 量子退火算法在金融投資組合優(yōu)化中的應(yīng)用案例-洞察闡釋
- 2024年陜西水務(wù)發(fā)展集團招聘真題
- 2024年嘉興市嘉善教育系統(tǒng)招聘教師真題
- 幼兒園水餃的活動方案
- 殯儀館物業(yè)服務(wù)方案
- 科技助力植樹節(jié):無人機、機器人種樹新趨勢
- 沖刺高考英語詞性轉(zhuǎn)換(易錯)背誦版默寫版(各版本通用)
- 采購人員廉潔從業(yè)課件培訓(xùn)
- 偷越國(邊)境罪與非法出入境罪
- 人工智能在財務(wù)管理中的應(yīng)用
- 幼兒園公開課:小班科學(xué)游戲《猜猜是誰的尾巴》原版超清課件
- 小貓的生物學(xué)
- 靜脈治療管理課件
- 磚混廠房改鋼結(jié)構(gòu)施工方案
- 2022年失業(yè)保險基金績效評價報告(最終稿)
評論
0/150
提交評論