您好,登錄后才能下訂單哦!
這篇文章主要介紹“Python進程池與進程鎖常用的方法有哪些”,在日常操作中,相信很多人在Python進程池與進程鎖常用的方法有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Python進程池與進程鎖常用的方法有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
上一章節關于進程的問題我們提到過,進程創建太多的情況下就會對資源消耗過大。為了避免出現這種情況,我們就需要固定進程的數量,這時候就需要進程池的幫助。
我們可以認為進程池就是一個池子,在這個池子里提前創建好一定數量的進程。見下圖:
比如這個紅色矩形陣列就代表一個進程池子,在這個池子中有6個進程。這6個進程會伴隨進程池一起被創建,不僅如此,我們在學習面向對象的生命周期的時候曾經說過,每個實例化對象在使用完成之后都會被內存管家回收。
我們的進程也會伴隨著創建與關閉的過程而被內存管家回收,每一個都是如此,創建于關閉進程的過程也會消耗一定的性能。而進程池中的進程當被創建之后就不會被關閉,可以一直被重復使用,從而避免了創建于關閉的資源消耗,也避免了創建于關閉的反復操作提高了效率。
當然,當我們執行完程序進程池關閉的時候,進程也隨之關閉。
當我們有任務需要被執行的時候,會判斷當前的進程池當中有沒有空閑的進程(所謂空閑的進程其實就是進程池中沒有執行任務的進程)。有進程處于空閑狀態的情況下,任務會找到進程執行該任務。如果當前進程池中的進程都處于非空閑狀態,則任務就會進入等待狀態,直到進程池中有進程處于空閑狀態才會進出進程池從而執行該任務。
這就是進程池的作用。
函數名 | 介紹 | 參數 | 返回值 |
---|---|---|---|
Pool | 進程池的創建 | Processcount | 進程池對象 |
Pool功能介紹:通過調用 "multiprocessing" 模塊的 "Pool" 函數來幫助我們創建 "進程池對象" ,它有一個參數 "Processcount" (一個整數),代表我們這個進程池中創建幾個進程。
當創建了進程池對象之后,我們要對它進程操作,讓我們來看一下都有哪些常用方法(函數)。
函數名 | 介紹 | 參數 | 返回值 |
---|---|---|---|
apply_async | 任務加入進程池(異步) | func,args | 無 |
close | 關閉進程池 | 無 | 無 |
join | 等待進程池任務結束 | 無 | 無 |
apply_async 函數:它的功能是將任務加入到進程池中,并且是通過異步實現的。
異步
這個知識我們還沒有學習,先不用關心它到底是什么意思。它有兩個參數:func 與 agrs
, func 是加入進程池中工作的函數;args 是一個元組,代表著簽一個函數的參數,這和我們創建并使用一個進程是完全一致的。close 函數:當我們使用完進程池之后,通過調用 close 函數可以關閉進程池。它沒有任何的參數,也沒有任何的返回值。
join 函數:它和我們上一章節學習的 創建進程的 join 函數中方法是一致的。只有進程池中的任務全部執行完畢之后,才會執行后續的任務。不過一般它會伴隨著進程池的關閉(
close 函數
)才會使用。
接下里我們在 Pycharm 中創建一個腳本,練習一下關于進程池的使用方法。
定義一個函數,打印輸出該函數 每次被執行的次數 與 該次數的進程號
定義進程池的數量,每一次的執行進程數量最多為該進程池設定的進程數
示例代碼如下:
# coding:utf-8import osimport timeimport multiprocessingdef work(count): # 定義一個 work 函數,打印輸出 每次執行的次數 與 該次數的進程號 print('\'work\' 函數 第 {} 次執行,進程號為 {}'.format(count, os.getpid())) time.sleep(3) # print('********')if __name__ == '__main__': pool = multiprocessing.Pool(3) # 定義進程池的進程數量,同一時間每次執行最多3個進程 for i in range(21): pool.apply_async(func=work, args=(i,)) # 傳入的參數是元組,因為我們只有一個 i 參數,所以我們要寫成 args=(i,) time.sleep(15) # 這里的休眠時間是必須要加上的,否則我們的進程池還未運行,主進程就已經運行結束,對應的進程池也會關閉。
運行結果如下:
從上圖中我們可以看到每一次都是一次性運行三個進程,每一個進程的進程號是不一樣的,但仔細看會發現存在相同的進程號,這說明進程池的進程號在被重復利用。這證明我們上文介紹的內容,進程池中的進程不會被關閉,可以反復使用。
而且我們還可以看到每隔3秒都會執行3個進程,原因是我們的進程池中只有3個進程;雖然我們的 for 循環
中有 21 個任務,work 函數會被執行21次,但是由于我們的進程池中只有3個進程。所以當執行了3個任務之后(休眠3秒),后面的任務等待進程池中的進程處于空閑狀態之后才會繼續執行。
同樣的,進程號在順序上回出現一定的區別,原因是因為我們使用的是一種 異步
的方法(異步即非同步)。這就導致 work 函數
一起執行的三個任務會被打亂順序,這也是為什么我們的進程號出現順序不一致的原因。(更多的異步知識我們會在異步的章節進行詳細介紹
)
進程池的原理: 上述腳本的案例證實了我們進程池關于進程的限制,只有當我們進程池中的進程處于空閑狀態的時候才會將進程池外等待的任務扔到進程池中工作。
在上文的腳本中, 我們使用 time.sleep(15)
幫助我們將主進程阻塞15秒鐘再次退出,所以給了我們進程池足夠的時間完成我們的 work() 函數的循環任務。
如果沒有 time.sleep(15)
這句話又怎么辦呢,其實這里就可以使用進程的 join
函數了。不過上文我們也提到過,進程的 join()
函數一般都會伴隨進程池的關閉(close 函數
)來使用。接下來,我們就將上文腳本中的 time.sleep(15)
替換成 join()
函數試一下。
示例代碼如下:
# coding:utf-8import osimport timeimport multiprocessingdef work(count): # 定義一個 work 函數,打印輸出 每次執行的次數 與 該次數的進程號 print('\'work\' 函數 第 {} 次執行,進程號為 {}'.format(count, os.getpid())) time.sleep(3) # print('********')if __name__ == '__main__': pool = multiprocessing.Pool(3) # 定義進程池的進程數量,同一時間每次執行最多3個進程 for i in range(21): pool.apply_async(func=work, args=(i,)) # 傳入的參數是元組,因為我們只有一個 i 參數,所以我們要寫成 args=(i,) # time.sleep(15) pool.close() pool.join()
運行結果如下:
從上面的動圖我們可以看出,work()
函數的任務與進程池中的進程與使用 time.sleep(15)
的運行結果一致。
PS:如果我們的主進程會一直執行,不會退出。那么我們并不需要添加 close() 與 join() 函數
,可以讓進程池一直啟動著,直到有任務進來就會執行。
在后面學習 WEB 開發之后,不退出主進程進行工作是家常便飯。還有一些需要長期執行的任務也不會關閉,但要是只有一次性執行的腳本,就需要添加 close() 與 join() 函數
來保證進程池的任務全部完成之后主進程再退出。當然,如果主進程關閉了,就不會再接受新的任務了,也就代表了進程池的終結。
接下來再看一個例子,在
work 函數
中加入一個 return。這里大家可能會有一個疑問,在上一章節針對進程的知識點明明說的是
進程無法獲取返回值
,那么這里的work()
函數增加的return
又有什么意義呢?其實不然,在我們的使用進程池的
apply_async
方法時,是通過異步的方式實現的,而異步是可以獲取返回值的。針對上述腳本,我們在for循環
中針對每一個異步apply_async
添加一個變量名,從而獲取返回值。
示例代碼如下:
# coding:utf-8import osimport timeimport multiprocessingdef work(count): # 定義一個 work 函數,打印輸出 每次執行的次數 與 該次數的進程號 print('\'work\' 函數 第 {} 次執行,進程號為 {}'.format(count, os.getpid())) time.sleep(3) return '\'work\' 函數 result 返回值為:{}, 進程ID為:{}'.format(count, os.getpid())if __name__ == '__main__': pool = multiprocessing.Pool(3) # 定義進程池的進程數量,同一時間每次執行最多3個進程 results = [] for i in range(21): result = pool.apply_async(func=work, args=(i,)) # 傳入的參數是元組,因為我們只有一個 i 參數,所以我們要寫成 args=(i,) results.append(result) for result in results: print(result.get()) # 可以通過這個方式返回 apply_async 的返回值, # 通過這種方式也不再需要 使用 close()、join() 函數就可以正常執行。 # time.sleep(15) # 這里的休眠時間是必須要加上的,否則我們的進程池還未運行,主進程就已經運行結束,對應的進程池也會關閉。 # pool.close() # pool.join()
運行結果如下:
從運行結果可以看出,首先 work()
函數被線程池的線程執行了一遍,當第一組任務執行完畢緊接著執行第二次線程池任務的時候,打印輸出了 apply_async
的返回值,證明返回值被成功的返回了。然后繼續下一組的任務…
這些都是主要依賴于 異步
,關于 異步
的更多知識會在 異步
的章節進行詳細的介紹。
鎖:大家都知道,我們可以給一個大門上鎖。
結合這個場景來舉一個例子:比如現在有多個進程同時沖向一個 "大門"
,當前門內是沒有 "人"的(其實就是進程),鎖也沒有鎖上。當有一個進程進去之后并且把 “門” 鎖上了,這時候門外的那些進程是進不來的。在門內的 “人” ,可以在 “門” 內做任何事情且不會被干擾。當它出來之后,會解開門鎖。這時候又有一個 “人” 進去了門內,并且重復這樣的操作,這就是 進程鎖
。它可以讓鎖后面的工作只能被一個任務來處理,只有它解鎖之后下一個任務才會進入,這就是 “鎖” 的概念。
而 進程鎖
就是僅針對于 進程
有效的鎖,當進程的任務開始之后,就會被上一把 “鎖”;與之對應的是 線程鎖
,它們的原理幾乎是一樣的。
進程鎖的使用方法:
通過 multiprocessing 導入 Manager 類
from multiprocessing import Manager
然后實例化 Manager
manager = Manager()
再然后通過實例化后的 manager 調用 它的 Lock() 函數
lock = manager.Lock()
接下來,就需要操作這個 lock 對象的函數
函數名 介紹 參數 返回值 acquire 上鎖 無 無 release 解鎖(開鎖) 無 無
代碼示例如下:
# coding:utf-8import osimport timeimport multiprocessingdef work(count, lock): # 定義一個 work 函數,打印輸出 每次執行的次數 與 該次數的進程號,增加線程鎖。 lock.acquire() # 上鎖 print('\'work\' 函數 第 {} 次執行,進程號為 {}'.format(count, os.getpid())) time.sleep(3) lock.release() # 解鎖 return '\'work\' 函數 result 返回值為:{}, 進程ID為:{}'.format(count, os.getpid())if __name__ == '__main__': pool = multiprocessing.Pool(3) # 定義進程池的進程數量,同一時間每次執行最多3個進程 manager = multiprocessing.Manager() lock = manager.Lock() results = [] for i in range(21): result = pool.apply_async(func=work, args=(i, lock)) # 傳入的參數是元組,因為我們只有一個 i 參數,所以我們要寫成 args=(i,) # results.append(result) # time.sleep(15) # 這里的休眠時間是必須要加上的,否則我們的進程池還未運行,主進程就已經運行結束,對應的進程池也會關閉。 pool.close() pool.join()
執行結果如下:
從上圖中,可以看到每一次只有一個任務會被執行。由于每一個進程會被阻塞 3秒鐘,所以我們的進程執行的非常慢。這是因為每一個進程進入到 work() 函數中,都會執行 上鎖、阻塞3秒、解鎖
的過程,這樣就完成了一個進程的工作。下一個進程任務開始,重復這個過程… 這就是 進程鎖的概念
。
其實進程鎖還有很多種方法,在 multiprocessing
中有一個直接使用的鎖,就是 ``from multiprocessing import Lock。這個
Lock的鎖使用和我們剛剛介紹的
Manager` 的鎖的使用有所區別。
鎖
的使用可以讓我們對某個任務 在同一時間只能對一個進程進行開發,但是 鎖也不可以亂用
。因為如果某些原因造成 鎖沒有正常解開
,就會造成死鎖
的現象,這樣就無法再進行操作了。
因為 鎖如果解不開
,后面的任務也就沒有辦法繼續執行任務,所以使用鎖一定要謹慎。
到此,關于“Python進程池與進程鎖常用的方法有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。