1. 程式人生 > >11.python並發入門(part11 進程同步鎖,以及進程池,以及callback的概念)

11.python並發入門(part11 進程同步鎖,以及進程池,以及callback的概念)

python lock 進程鎖 回調函數 callback 進程池

一、關於進程鎖。

其實關於進程鎖沒啥好講的了,作用跟線程的互斥鎖(又叫全局鎖也叫同步鎖)作用幾乎是一樣的。

都是用來給公共資源上鎖,進行數據保護的。

當一個進程想去操作一個公共資源,它就可以給公共資源進程“上鎖”的操作,其他進程如果也想去訪問或者操作這個公共資源,那麽其他的進程只能阻塞,等待剛剛的進程把鎖釋放,下一個進程才可以對這個公共資源進行操作。

下面是個關於進程鎖的使用示範:

#!/usr/local/bin/python2.7

# -*- coding:utf-8 -*-

import multiprocessing

def func1(lock_1,name):

lock_1.acquire()

print "hello %s" %(name) #當執行到這句話的時候,就不再是並行了,和線程中鎖的概念是一樣的。

lock_1.release()

if __name__ == ‘__main__‘:

lock = multiprocessing.Lock()

for num in range(10):

multiprocessing.Process(target=func1,args=(lock,num)).start()


輸出結果:

hello 0

hello 1

hello 2

hello 3

hello 4

hello 5

hello 6

hello 7

hello 8

hello 9


!!補充下,其實在進程中,也可以使用RLock遞歸鎖,信號量,event等各種鎖,用法基本和多線程是一樣的。


二、關於進程池。

什麽是進程池呢?

進程池就是程序中維護的一個進程序列,當需要使用進程的時候,就會從進程池中獲取一個進程,當進程池中不再有進程的時候,程序就會阻塞,一直等到有新的進程加入到進程池為止。

那麽進程池有什麽好處?可以看看下面這個例子。

假如說有個函數,這個函數的內容需要被執行100次,才能完成我們想要完成的任務。

我們假定運行一次這個函數要10秒鐘,如果使用單進程單線程的模式串行執行,執行這個函數100次要花費1000秒的時間,效率非常低!

那麽多線程呢?我們可以同時開100個線程,每個線程去執行一次這個函數,也很快可以執行結束,但是前提是,這個函數所執行的操作必須屬於I/O密集型,這樣才可以發揮出多線程的高效率,如果這個函數所執行的操作是計算密集型的呢?(如果計算密集型的任務交給多線程去做,反而可能會降低效率,這是因為全局解釋器鎖的特性造成的)那我們就需要去考慮去開多進程了。

好,那接下來我們用多線程的方式去處理這個問題。

這個函數既然要執行100次,那麽我幹脆去開100個進程,讓這100個函數並行去執行這個函數。

這樣確實可以大大提高效率,但是,無論是開100個進程也好,或者說是開100個線程也好,這樣做開銷實在是太大了!!!同一個進程裏面的多個線程還好,數據資源在多個進程之間都是共享的,線程和線程之間每次切換,值需要保存當前的執行狀態就可以了,但是線程呢,線程和線程之間的數據都是獨立的,多個進程要想實現數據共享,就要給每個進程復制一份數據資源,開100個線程,數據資源就要被復制100份!開銷實在太大了!


其實上面的這些方法都不太好,但是可以折中考慮,創建一個線程池。

接下來我們來分析下進程池的好處。

假設說,我們創建了一個進程池,創建這個進程池的時候,指定這個進程池中最多可以容納10個進程,也就意味著一次的最大並發是10個進程一起去執行這個函數。

接下來,這10個進程都把自己的事情做完了,當然,這10個進程不可能完全是一起執行完畢的(除非你的cpu有十核),肯定有先執行完任務進程。

那麽換種說法吧,這10個並發運行的進程中,現在假如有一個進程已經運行完畢,那麽這個進程池中還剩下9個進程,我的進程池中現在空出來了一個位置,空出來的這個位置,還可以繼續開一個新的進程去工作(只要進程池中有空位,就可以開啟新的進程去繼續工作。)

所以說,通過以上的例子,可以明確一點,進程池的好處,就是可以維護一個進程工作的最大並發量,比如我們手動設置進程池中最大可以容納10個進程,那麽在程序運行時,運行的進程數量不會高於10個,也不會低於10個,可以一直維持這個最大並發量。

雖然沒辦法10秒執行100次這個函數,但是,使用了進程池,肯定要比串行執行要快的多!

所以說,進程池是一個折中的解決方案。


下面是進程池的使用示例:

#!/usr/local/bin/python2.7

# -*- coding:utf-8 -*-

import multiprocessing

import os

import time

def func1(value):

time.sleep(5)

print "---------->%s" %(value)

return value+100

def display_pro_info(value):

print os.getpid()

print os.getppid()

print "log:%s" %(value)

pro_pool = multiprocessing.Pool(10) #創建線程池對象,並指定並最大並發量,如果不指定,會按照當前計算機的cpu核數來自動調整.

display_pro_info(1)

print "--------------->"

for i in range(100): #創建100個線程

# pro_pool.apply_async(func=func1,args=(i,)) #這一步可以暫時理解為往進程池裏添加進程,本文後面會對該步驟做詳細描述。

pro_pool.apply_async(func=func1,args=(i,),callback=display_pro_info) #這裏使用了callback回調函數,什麽是回調函數,本文後面會介紹。

pro_pool.close() #在這需要註意一點,就是使用進程池,一定要先close再join,否則就會報錯。先close後join這個順序是固定的!!

pro_pool.join() #沒有join的話,進程池裏的進程是不會運行的。

print "the end!"


下面輸出一下這個程序的運行結果:

44720

40833

log:0

--------------->

---------->0

---------->1

---------->3

---------->2

---------->5

---------->9

---------->4

---------->7

---------->6

---------->8

44720

40833

log:100

44720

40833

log:101

44720

40833

log:103

44720

40833

log:102

44720

40833

log:109

44720

40833

log:104

44720

40833

log:107

44720

40833

log:106

44720

40833

log:105

44720

40833

log:108

......

the end!


下面開始對代碼進行分析,並且對進程池中一些常用的方法進行講解:

  1. 首先來說說,在進程池中添加進程的兩種模式。

分別是apply(同步執行模式)和apply_async(異步執行模式)。


1.1 apply(同步執行模式)

這種模式一般情況下沒人會去用,如果進程池使用了這種模式,當進程池的第一個進程執行完畢後,才會執行第二個進程,第二個進程執行完畢後,在執行第三個進程....(也就是說這種模式會阻塞主進程!),無法實現並行效果。(不止如此,這種模式還不支持callback回調函數。)

1.2 apply_async (異步執行模式)

異步的執行模式,才是可以實現並行效果的模式,支持callback回調函數,當一個進程沒有執行完畢,沒有返回結果,異步執行的模式並不會對主進程進行阻塞!

補充一點!雖然 apply_async是非阻塞的,但其返回結果的get方法卻是阻塞的,如使用result.get()會阻塞主進程!


1.3 pro_pool.apply_async(func=func1,args=(i,),callback=display_pro_info)

語法和創建多線程多進程沒有什麽差別,func用於指定一個進程要運行的函數,args用來給函數傳參數,callback用來指定回調函數。


2.關於join,close,terminate。

2.1 join 主進程阻塞等待子進程運行結束後退出, join方法要在close或terminate之後使用。(換種說法就是,不加join方法,進程池裏的進程根本不會執行~)

2.2 close 關閉進程池,不再接受新的任務

2.3 terminate 直接結束進程,不再處理沒有處理的任務

#其實join和close比較常用。


3.關於callback函數的一個簡單說明。

在了解回調函數之前,需要註意!!callback函數是由主進程去調用的,並不是子進程!!從剛剛的示例中就可以看出結果!

某一個函數執行完畢後,某個函數或者動作執行成功後,再去執行的一個函數,並且之前執行成功的那個函數的返回值,會作為參數傳給callback函數。

讓回調函數在主進程下調用有什麽好處?

比如現在需要開10個線程去對數據庫中的數據進行操作,現在有個需求,就是對數據庫內部進行的操作都需要記錄一個日誌,我們可以把寫日誌這個函數做為一個回調(callback)函數。

在不使用callback函數之前,如果想去開10個進程去操作數據庫,這10個進程是並發執行的,每個進程都去操作數據庫後,同時去寫一個日誌文件,如果不加鎖的話很容易造成日誌文件的損壞,所以我們可以把寫日誌的函數去交給主進程去執行,當進程池中的10個進程運行結束後,主線程直接執行一個寫日誌的操作,這就是我理解的callback函數的用處。

拿進程池中的callback來舉例吧,pro_pool.apply_async(func=func1,args=(i,),callback=display_pro_info)當func1函數內部執行成功後(只要返回值不為假),display_pro_info這個函數就會被作為回調函數去執行(當然執行這個函數的是主進程!),func1的返回值會作為參數傳給display_pro_info這個函數。


最後補充一下,使用callback回調函數需要註意的幾點。

  1. 在執行回調函數之前的那個函數,必須有一個返回值!這個返回值有兩個用途,第一個用途是判斷這個函數是否執行成功,執行成功才可以執行callback函數,另一個用途就是這個返回值會作為參數傳給回調函數。

  2. callback回調函數本身必須接收一個參數,這個參數用來接收上一個函數執行完畢後的返回值。


本文出自 “reBiRTH” 博客,請務必保留此出處http://suhaozhi.blog.51cto.com/7272298/1926096

11.python並發入門(part11 進程同步鎖,以及進程池,以及callback的概念)