溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python進(jìn)程池與進(jìn)程鎖實(shí)例分析

發(fā)布時(shí)間:2022-04-11 10:55:11 來(lái)源:億速云 閱讀:92 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Python進(jìn)程池與進(jìn)程鎖實(shí)例分析”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Python進(jìn)程池與進(jìn)程鎖實(shí)例分析”吧!

進(jìn)程池

什么是進(jìn)程池

上一章節(jié)關(guān)于進(jìn)程的問題我們提到過,進(jìn)程創(chuàng)建太多的情況下就會(huì)對(duì)資源消耗過大。為了避免出現(xiàn)這種情況,我們就需要固定進(jìn)程的數(shù)量,這時(shí)候就需要進(jìn)程池的幫助。

我們可以認(rèn)為進(jìn)程池就是一個(gè)池子,在這個(gè)池子里提前創(chuàng)建好一定數(shù)量的進(jìn)程。見下圖:

Python進(jìn)程池與進(jìn)程鎖實(shí)例分析

比如這個(gè)紅色矩形陣列就代表一個(gè)進(jìn)程池子,在這個(gè)池子中有6個(gè)進(jìn)程。這6個(gè)進(jìn)程會(huì)伴隨進(jìn)程池一起被創(chuàng)建,不僅如此,我們?cè)趯W(xué)習(xí)面向?qū)ο蟮纳芷诘臅r(shí)候曾經(jīng)說(shuō)過,每個(gè)實(shí)例化對(duì)象在使用完成之后都會(huì)被內(nèi)存管家回收。

我們的進(jìn)程也會(huì)伴隨著創(chuàng)建與關(guān)閉的過程而被內(nèi)存管家回收,每一個(gè)都是如此,創(chuàng)建于關(guān)閉進(jìn)程的過程也會(huì)消耗一定的性能。而進(jìn)程池中的進(jìn)程當(dāng)被創(chuàng)建之后就不會(huì)被關(guān)閉,可以一直被重復(fù)使用,從而避免了創(chuàng)建于關(guān)閉的資源消耗,也避免了創(chuàng)建于關(guān)閉的反復(fù)操作提高了效率。

當(dāng)然,當(dāng)我們執(zhí)行完程序進(jìn)程池關(guān)閉的時(shí)候,進(jìn)程也隨之關(guān)閉。

當(dāng)我們有任務(wù)需要被執(zhí)行的時(shí)候,會(huì)判斷當(dāng)前的進(jìn)程池當(dāng)中有沒有空閑的進(jìn)程(所謂空閑的進(jìn)程其實(shí)就是進(jìn)程池中沒有執(zhí)行任務(wù)的進(jìn)程)。有進(jìn)程處于空閑狀態(tài)的情況下,任務(wù)會(huì)找到進(jìn)程執(zhí)行該任務(wù)。如果當(dāng)前進(jìn)程池中的進(jìn)程都處于非空閑狀態(tài),則任務(wù)就會(huì)進(jìn)入等待狀態(tài),直到進(jìn)程池中有進(jìn)程處于空閑狀態(tài)才會(huì)進(jìn)出進(jìn)程池從而執(zhí)行該任務(wù)。

這就是進(jìn)程池的作用。

進(jìn)程池的創(chuàng)建模塊 - multiprocessing

創(chuàng)建進(jìn)程池函數(shù) - Pool

函數(shù)名介紹參數(shù)返回值
Pool進(jìn)程池的創(chuàng)建Processcount進(jìn)程池對(duì)象

Pool功能介紹:通過調(diào)用 "multiprocessing" 模塊的 "Pool" 函數(shù)來(lái)幫助我們創(chuàng)建 "進(jìn)程池對(duì)象" ,它有一個(gè)參數(shù) "Processcount" (一個(gè)整數(shù)),代表我們這個(gè)進(jìn)程池中創(chuàng)建幾個(gè)進(jìn)程。

進(jìn)程池的常用方法

當(dāng)創(chuàng)建了進(jìn)程池對(duì)象之后,我們要對(duì)它進(jìn)程操作,讓我們來(lái)看一下都有哪些常用方法(函數(shù))。

函數(shù)名介紹參數(shù)返回值
apply_async任務(wù)加入進(jìn)程池(異步)func,args無(wú)
close關(guān)閉進(jìn)程池無(wú)無(wú)
join等待進(jìn)程池任務(wù)結(jié)束無(wú)無(wú)
  • apply_async 函數(shù):它的功能是將任務(wù)加入到進(jìn)程池中,并且是通過異步實(shí)現(xiàn)的。異步 這個(gè)知識(shí)我們還沒有學(xué)習(xí),先不用關(guān)心它到底是什么意思。它有兩個(gè)參數(shù):func 與 agrs , func 是加入進(jìn)程池中工作的函數(shù);args 是一個(gè)元組,代表著簽一個(gè)函數(shù)的參數(shù),這和我們創(chuàng)建并使用一個(gè)進(jìn)程是完全一致的。

  • close 函數(shù):當(dāng)我們使用完進(jìn)程池之后,通過調(diào)用 close 函數(shù)可以關(guān)閉進(jìn)程池。它沒有任何的參數(shù),也沒有任何的返回值。

  • join 函數(shù):它和我們上一章節(jié)學(xué)習(xí)的 創(chuàng)建進(jìn)程的 join 函數(shù)中方法是一致的。只有進(jìn)程池中的任務(wù)全部執(zhí)行完畢之后,才會(huì)執(zhí)行后續(xù)的任務(wù)。不過一般它會(huì)伴隨著進(jìn)程池的關(guān)閉(close 函數(shù))才會(huì)使用。

apply_async 函數(shù)演示案例

接下里我們?cè)?Pycharm 中創(chuàng)建一個(gè)腳本,練習(xí)一下關(guān)于進(jìn)程池的使用方法。

  • 定義一個(gè)函數(shù),打印輸出該函數(shù) 每次被執(zhí)行的次數(shù) 與 該次數(shù)的進(jìn)程號(hào)

  • 定義進(jìn)程池的數(shù)量,每一次的執(zhí)行進(jìn)程數(shù)量最多為該進(jìn)程池設(shè)定的進(jìn)程數(shù)

示例代碼如下:

# coding:utf-8


import os
import time
import multiprocessing

def work(count):    # 定義一個(gè) work 函數(shù),打印輸出 每次執(zhí)行的次數(shù) 與 該次數(shù)的進(jìn)程號(hào)
    print('\'work\' 函數(shù) 第 {} 次執(zhí)行,進(jìn)程號(hào)為 {}'.format(count, os.getpid()))
    time.sleep(3)
    # print('********')


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定義進(jìn)程池的進(jìn)程數(shù)量,同一時(shí)間每次執(zhí)行最多3個(gè)進(jìn)程
    for i in range(21):
        pool.apply_async(func=work, args=(i,))      # 傳入的參數(shù)是元組,因?yàn)槲覀冎挥幸粋€(gè) i 參數(shù),所以我們要寫成 args=(i,)

    time.sleep(15)      # 這里的休眠時(shí)間是必須要加上的,否則我們的進(jìn)程池還未運(yùn)行,主進(jìn)程就已經(jīng)運(yùn)行結(jié)束,對(duì)應(yīng)的進(jìn)程池也會(huì)關(guān)閉。

運(yùn)行結(jié)果如下:

Python進(jìn)程池與進(jìn)程鎖實(shí)例分析

從上圖中我們可以看到每一次都是一次性運(yùn)行三個(gè)進(jìn)程,每一個(gè)進(jìn)程的進(jìn)程號(hào)是不一樣的,但仔細(xì)看會(huì)發(fā)現(xiàn)存在相同的進(jìn)程號(hào),這說(shuō)明進(jìn)程池的進(jìn)程號(hào)在被重復(fù)利用。這證明我們上文介紹的內(nèi)容,進(jìn)程池中的進(jìn)程不會(huì)被關(guān)閉,可以反復(fù)使用。

而且我們還可以看到每隔3秒都會(huì)執(zhí)行3個(gè)進(jìn)程,原因是我們的進(jìn)程池中只有3個(gè)進(jìn)程;雖然我們的 for 循環(huán) 中有 21 個(gè)任務(wù),work 函數(shù)會(huì)被執(zhí)行21次,但是由于我們的進(jìn)程池中只有3個(gè)進(jìn)程。所以當(dāng)執(zhí)行了3個(gè)任務(wù)之后(休眠3秒),后面的任務(wù)等待進(jìn)程池中的進(jìn)程處于空閑狀態(tài)之后才會(huì)繼續(xù)執(zhí)行。

同樣的,進(jìn)程號(hào)在順序上回出現(xiàn)一定的區(qū)別,原因是因?yàn)槲覀兪褂玫氖且环N 異步 的方法(異步即非同步)。這就導(dǎo)致 work 函數(shù) 一起執(zhí)行的三個(gè)任務(wù)會(huì)被打亂順序,這也是為什么我們的進(jìn)程號(hào)出現(xiàn)順序不一致的原因。(更多的異步知識(shí)我們會(huì)在異步的章節(jié)進(jìn)行詳細(xì)介紹)

進(jìn)程池的原理: 上述腳本的案例證實(shí)了我們進(jìn)程池關(guān)于進(jìn)程的限制,只有當(dāng)我們進(jìn)程池中的進(jìn)程處于空閑狀態(tài)的時(shí)候才會(huì)將進(jìn)程池外等待的任務(wù)扔到進(jìn)程池中工作。

close 函數(shù)與 join 函數(shù) 演示

在上文的腳本中, 我們使用 time.sleep(15) 幫助我們將主進(jìn)程阻塞15秒鐘再次退出,所以給了我們進(jìn)程池足夠的時(shí)間完成我們的 work() 函數(shù)的循環(huán)任務(wù)。

如果沒有 time.sleep(15) 這句話又怎么辦呢,其實(shí)這里就可以使用進(jìn)程的 join 函數(shù)了。不過上文我們也提到過,進(jìn)程的 join() 函數(shù)一般都會(huì)伴隨進(jìn)程池的關(guān)閉(close 函數(shù))來(lái)使用。接下來(lái),我們就將上文腳本中的 time.sleep(15) 替換成 join() 函數(shù)試一下。

示例代碼如下:

# coding:utf-8


import os
import time
import multiprocessing

def work(count):    # 定義一個(gè) work 函數(shù),打印輸出 每次執(zhí)行的次數(shù) 與 該次數(shù)的進(jìn)程號(hào)
    print('\'work\' 函數(shù) 第 {} 次執(zhí)行,進(jìn)程號(hào)為 {}'.format(count, os.getpid()))
    time.sleep(3)
    # print('********')


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定義進(jìn)程池的進(jìn)程數(shù)量,同一時(shí)間每次執(zhí)行最多3個(gè)進(jìn)程
    for i in range(21):
        pool.apply_async(func=work, args=(i,))      # 傳入的參數(shù)是元組,因?yàn)槲覀冎挥幸粋€(gè) i 參數(shù),所以我們要寫成 args=(i,)

    # time.sleep(15) 
    pool.close()
    pool.join()

運(yùn)行結(jié)果如下:

Python進(jìn)程池與進(jìn)程鎖實(shí)例分析

從上面的動(dòng)圖我們可以看出,work() 函數(shù)的任務(wù)與進(jìn)程池中的進(jìn)程與使用 time.sleep(15)的運(yùn)行結(jié)果一致。

PS:如果我們的主進(jìn)程會(huì)一直執(zhí)行,不會(huì)退出。那么我們并不需要添加 close() 與 join() 函數(shù) ,可以讓進(jìn)程池一直啟動(dòng)著,直到有任務(wù)進(jìn)來(lái)就會(huì)執(zhí)行。

在后面學(xué)習(xí) WEB 開發(fā)之后,不退出主進(jìn)程進(jìn)行工作是家常便飯。還有一些需要長(zhǎng)期執(zhí)行的任務(wù)也不會(huì)關(guān)閉,但要是只有一次性執(zhí)行的腳本,就需要添加 close() 與 join() 函數(shù) 來(lái)保證進(jìn)程池的任務(wù)全部完成之后主進(jìn)程再退出。當(dāng)然,如果主進(jìn)程關(guān)閉了,就不會(huì)再接受新的任務(wù)了,也就代表了進(jìn)程池的終結(jié)。

接下來(lái)再看一個(gè)例子,在 work 函數(shù) 中加入一個(gè) return。

這里大家可能會(huì)有一個(gè)疑問,在上一章節(jié)針對(duì)進(jìn)程的知識(shí)點(diǎn)明明說(shuō)的是 進(jìn)程無(wú)法獲取返回值,那么這里的 work() 函數(shù)增加的 return 又有什么意義呢?

其實(shí)不然,在我們的使用進(jìn)程池的 apply_async 方法時(shí),是通過異步的方式實(shí)現(xiàn)的,而異步是可以獲取返回值的。針對(duì)上述腳本,我們?cè)?for循環(huán)中針對(duì)每一個(gè)異步 apply_async 添加一個(gè)變量名,從而獲取返回值。

示例代碼如下:

# coding:utf-8


import os
import time
import multiprocessing

def work(count):    # 定義一個(gè) work 函數(shù),打印輸出 每次執(zhí)行的次數(shù) 與 該次數(shù)的進(jìn)程號(hào)
    print('\'work\' 函數(shù) 第 {} 次執(zhí)行,進(jìn)程號(hào)為 {}'.format(count, os.getpid()))
    time.sleep(3)
    return '\'work\' 函數(shù) result 返回值為:{}, 進(jìn)程ID為:{}'.format(count, os.getpid())


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定義進(jìn)程池的進(jìn)程數(shù)量,同一時(shí)間每次執(zhí)行最多3個(gè)進(jìn)程
    results = []
    for i in range(21):
        result = pool.apply_async(func=work, args=(i,))      # 傳入的參數(shù)是元組,因?yàn)槲覀冎挥幸粋€(gè) i 參數(shù),所以我們要寫成 args=(i,)
        results.append(result)

    for result in results:
        print(result.get())     # 可以通過這個(gè)方式返回 apply_async 的返回值,
                                # 通過這種方式也不再需要 使用 close()、join() 函數(shù)就可以正常執(zhí)行。

    # time.sleep(15)      # 這里的休眠時(shí)間是必須要加上的,否則我們的進(jìn)程池還未運(yùn)行,主進(jìn)程就已經(jīng)運(yùn)行結(jié)束,對(duì)應(yīng)的進(jìn)程池也會(huì)關(guān)閉。
    # pool.close()
    # pool.join()

運(yùn)行結(jié)果如下:

Python進(jìn)程池與進(jìn)程鎖實(shí)例分析

從運(yùn)行結(jié)果可以看出,首先 work() 函數(shù)被線程池的線程執(zhí)行了一遍,當(dāng)?shù)谝唤M任務(wù)執(zhí)行完畢緊接著執(zhí)行第二次線程池任務(wù)的時(shí)候,打印輸出了 apply_async 的返回值,證明返回值被成功的返回了。然后繼續(xù)下一組的任務(wù)…

這些都是主要依賴于 異步 ,關(guān)于 異步 的更多知識(shí)會(huì)在 異步 的章節(jié)進(jìn)行詳細(xì)的介紹。

進(jìn)程鎖

進(jìn)程鎖的概念

鎖:大家都知道,我們可以給一個(gè)大門上鎖。

結(jié)合這個(gè)場(chǎng)景來(lái)舉一個(gè)例子:比如現(xiàn)在有多個(gè)進(jìn)程同時(shí)沖向一個(gè) "大門" ,當(dāng)前門內(nèi)是沒有 "人"的(其實(shí)就是進(jìn)程),鎖也沒有鎖上。當(dāng)有一個(gè)進(jìn)程進(jìn)去之后并且把 “門” 鎖上了,這時(shí)候門外的那些進(jìn)程是進(jìn)不來(lái)的。在門內(nèi)的 “人” ,可以在 “門” 內(nèi)做任何事情且不會(huì)被干擾。當(dāng)它出來(lái)之后,會(huì)解開門鎖。這時(shí)候又有一個(gè) “人” 進(jìn)去了門內(nèi),并且重復(fù)這樣的操作,這就是 進(jìn)程鎖。它可以讓鎖后面的工作只能被一個(gè)任務(wù)來(lái)處理,只有它解鎖之后下一個(gè)任務(wù)才會(huì)進(jìn)入,這就是 “鎖” 的概念。

而 進(jìn)程鎖 就是僅針對(duì)于 進(jìn)程 有效的鎖,當(dāng)進(jìn)程的任務(wù)開始之后,就會(huì)被上一把 “鎖”;與之對(duì)應(yīng)的是 線程鎖 ,它們的原理幾乎是一樣的。

進(jìn)程鎖的加鎖與解鎖

進(jìn)程鎖的使用方法:

通過 multiprocessing 導(dǎo)入 Manager 類

from multiprocessing import Manager

然后實(shí)例化 Manager

manager = Manager()

再然后通過實(shí)例化后的 manager 調(diào)用 它的 Lock() 函數(shù)

lock = manager.Lock()

接下來(lái),就需要操作這個(gè) lock 對(duì)象的函數(shù)

函數(shù)名介紹參數(shù)返回值
acquire上鎖無(wú)無(wú)
release解鎖(開鎖)無(wú)無(wú)

代碼示例如下:

# coding:utf-8


import os
import time
import multiprocessing


def work(count, lock):    # 定義一個(gè) work 函數(shù),打印輸出 每次執(zhí)行的次數(shù) 與 該次數(shù)的進(jìn)程號(hào),增加線程鎖。
    lock.acquire()        # 上鎖
    print('\'work\' 函數(shù) 第 {} 次執(zhí)行,進(jìn)程號(hào)為 {}'.format(count, os.getpid()))
    time.sleep(3)
    lock.release()        # 解鎖
    return '\'work\' 函數(shù) result 返回值為:{}, 進(jìn)程ID為:{}'.format(count, os.getpid())


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定義進(jìn)程池的進(jìn)程數(shù)量,同一時(shí)間每次執(zhí)行最多3個(gè)進(jìn)程
    manager = multiprocessing.Manager()
    lock = manager.Lock()
    results = []
    for i in range(21):
        result = pool.apply_async(func=work, args=(i, lock))      # 傳入的參數(shù)是元組,因?yàn)槲覀冎挥幸粋€(gè) i 參數(shù),所以我們要寫成 args=(i,)
        # results.append(result)


    # time.sleep(15)      # 這里的休眠時(shí)間是必須要加上的,否則我們的進(jìn)程池還未運(yùn)行,主進(jìn)程就已經(jīng)運(yùn)行結(jié)束,對(duì)應(yīng)的進(jìn)程池也會(huì)關(guān)閉。
    pool.close()
    pool.join()

執(zhí)行結(jié)果如下:

Python進(jìn)程池與進(jìn)程鎖實(shí)例分析

從上圖中,可以看到每一次只有一個(gè)任務(wù)會(huì)被執(zhí)行。由于每一個(gè)進(jìn)程會(huì)被阻塞 3秒鐘,所以我們的進(jìn)程執(zhí)行的非常慢。這是因?yàn)槊恳粋€(gè)進(jìn)程進(jìn)入到 work() 函數(shù)中,都會(huì)執(zhí)行 上鎖、阻塞3秒、解鎖 的過程,這樣就完成了一個(gè)進(jìn)程的工作。下一個(gè)進(jìn)程任務(wù)開始,重復(fù)這個(gè)過程… 這就是 進(jìn)程鎖的概念。

其實(shí)進(jìn)程鎖還有很多種方法,在 multiprocessing 中有一個(gè)直接使用的鎖,就是 ``from multiprocessing import Lock。這個(gè)Lock的鎖使用和我們剛剛介紹的Manager` 的鎖的使用有所區(qū)別。(這里不做詳細(xì)介紹,感興趣的話可以自行拓展一下。)

鎖 的使用可以讓我們對(duì)某個(gè)任務(wù) 在同一時(shí)間只能對(duì)一個(gè)進(jìn)程進(jìn)行開發(fā),但是 鎖也不可以亂用 。因?yàn)槿绻承┰蛟斐?鎖沒有正常解開 ,就會(huì)造成死鎖的現(xiàn)象,這樣就無(wú)法再進(jìn)行操作了。

因?yàn)?鎖如果解不開 ,后面的任務(wù)也就沒有辦法繼續(xù)執(zhí)行任務(wù),所以使用鎖一定要謹(jǐn)慎。

 

到此,相信大家對(duì)“Python進(jìn)程池與進(jìn)程鎖實(shí)例分析”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI