day25 多進程

day25 多進程

今日內容

  1. 多任務
  2. 多進程
  3. 進程池

昨日回顧

  1. 魔法方法
    1. __init__
    2. __new__
    3. __str__
    4. __len__
    5. __del__
    6. __eq__
    7. __hash__
  2. 異常處理
    1. try...except...
    2. try...except...except...
    3. try...except...else...
    4. finally

今日內容詳細

多任務

咱們打開任務管理器,就會發現,同一時刻用不少程序在運行。這種是的計算機能夠同時處理多個任務的現象就是多任務處理。數組

1571133491145

有了多任務處理,咱們才能作到在聽歌的同時使用QQ聊天、辦公和下載文件。併發

多任務處理的幾個重要概念app

  • 串行:程序依照前後順序,逐個執行,一次只能執行一個任務
  • 並行:多個程序同時執行,須要CPU數目多於任務數才能實現
  • 併發:在一段時間內,多個任務一塊兒執行,由於時間很短,看起來就像同時執行同樣,程序數能夠多與CPU數
  • 同步:一個程序執行完再調用另外一個程序,要等程序執行完畢才能開始下一個程序
  • 異步:一個程序執行中就調用另外一個程序,不須要程序執行完就能夠開啓下一個程序,只有異步的狀況才能實現併發
  • 阻塞:CPU不工做
  • 非阻塞:CPU工做

多進程

程序,是一個指令的集合,也就是咱們寫的一套代碼。異步

進程則是指正在執行的程序。換句話說,當你運行一個程序,你就啓動了一個進程。async

  • 編寫玩的代碼,沒有運行時,稱爲程序,正在運行的代碼,稱爲進程
  • 程序是死的(靜態的),繼承是活的(動態的)

操做系統輪流讓各個任務交替執行。因爲CPU的執行速度實在是太快了,咱們感受就像全部任務都在同時執行同樣。函數

多進程,就是讓多個程序同時運行。多進程中,每一個進程中全部數據(包括全局變量)都各擁有一份,互不影響。測試

程序開始運行時,會首先建立一個主進程(父進程)操作系統

在主進程下,咱們能夠建立新的進程,也就是子進程。code

子進程依賴於主進程,若是主進程結束,程序會退出,子進程也就自動終止。

1571139706229

Python提供了很是好用的多進程包multiprocessing。藉助這個包,能夠輕鬆完成單進程到併發執行的轉換:

from multiprocessing import Process
def land_occupation(name):
    print(f'{name}佔領銅鑼灣')
def grab_the_ground():
    print('搶佔鉢蘭街')
# Windows系統須要這行代碼避免迭代導入的異常
if __name__ == '__main__':
    print('主進程啓動,幫派創建')
    haonan = Process(target=land_occupation, args=('陳浩南',), name='陳浩南佔地盤的子進程')
    # target表示調用的方法,args表示調用方法的位置參數元組
    # 須要注意的是,若是元組只有一個元素,括號中須要加一個逗號
    shisanmei = Process(target=grab_the_ground, name='十三妹搶地盤的子進程')
    print(haonan.name, shisanmei.name)
    print(haonan.pid, shisanmei.pid)
    haonan.start()
    shisanmei.start()
    print(haonan.pid, shisanmei.pid)
    haonan.join()
    shisanmei.join()
    print('程序結束')
    
輸出的結果爲:
主進程啓動,幫派創建
陳浩南佔地盤的子進程 十三妹搶地盤的子進程
None None
19932 19160
搶佔鉢蘭街
陳浩南佔領銅鑼灣
程序結束

在上面的代碼中,咱們把建立的進程和調用進程的代碼都寫到了if __name__ == '__main__':的子句中。

這是由於在Windows中,子進程會自動import啓動它的文件。而在import時,文件中的代碼會被自動執行。當執行到建立子進程的代碼時,又要從新導入本身。這就陷入了一個導入本身的死循環,而後就會報錯。

1571185261271

爲了不報錯,咱們把這些代碼放到if __name__ == '__main__':的子句中,這樣當建立子進程時,導入代碼就不會從新加載建立子進程的語句了。

1571185830236

不過最好仍是將操做多進程的代碼封裝到函數中,這樣會避免不少麻煩。

Process(target, name, args)參數介紹

  • target表示調用的對象,即子進程要執行的任務
  • args表示要調用對象的位置參數組成的元組(經測試,只要是可迭代對象均可以。須要注意的是,傳入的內容會被迭代運行,因此要注意避免誤傳參數的問題)
  • name爲本身成的名字,默認爲Process-1等等

Process類經常使用方法

  • .start():啓動進程,並調用子進程中的.run()方法
  • .run():進程啓動時調用的方法,正是它去調用target參數指定的函數。
  • .terminate():(瞭解便可)強制終止進程,不會進行任何清理操做,進程會一直佔用內存空間
  • .is_alive():若是進程仍在運行,返回True,不然返回False。用來判斷進程是否還在運行
  • .join([timeout]):中近程等待子進程終止,timeout時可選的超時時間

Process類經常使用屬性:

  • name:當前進程實例的別名,默認爲Process-N,N爲從1開始遞增的整數
  • pid:當前進程實例的PID值,也就是操做系統爲該進程進行的編號。pid在程序就緒以前(start方法未執行)的狀態時值爲None。只有當進程就緒,纔會被分配PID值。

全局變量在多個進程中是不共享的。進程之間的數據相互獨立,默認狀況下不會相互影響:

from multiprocessing import Process
num = 10
def r1():
    global num
    num += 5
    print('在進程一中,num的值爲:', num)
def r2():
    global num
    num += 10
    print('在進程二中,num的值爲:', num)
    
if __name__ == '__main__':
    p1 = Process(target=r1)
    p2 = Process(target=r2)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('程序結束前,主進程中num的值爲:', num)
    
輸出的結果爲:
在進程一中,num的值爲: 15
在進程二中,num的值爲: 20
程序結束前,主進程中num的值爲: 10

須要注意的是,每次建立進程對象時,都會import當前文件。這就致使在if __name__ == '__main__'語句以後對num進行的修改操做不會被加載到進程對象的內存中。換句話說,子進程可以加載全局變量在if __name__ == '__main__'語句以外的修改,卻沒法加載其內部的修改:

from multiprocessing import Process
num = 10
def r1():
    global num
    num += 5
    print('在進程一中,num的值爲:', num)
def r2():
    global num
    num += 10
    print('在進程二中,num的值爲:', num)
# 在外部修改全局變量num
num += 500
if __name__ == '__main__':
    # 在內部修改全局變量
    num += 1000
    p1 = Process(target=r1)
    p2 = Process(target=r2)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('程序結束前,主進程中num的值爲:', num)
    
輸出的結果爲:
在進程一中,num的值爲: 515
在進程二中,num的值爲: 520
程序結束前,主進程中num的值爲: 1510

在子進程中,1000都沒有被加上,可是500被加上了。由此能夠驗證前面的觀點。

除了直接使用Process建立類對象以外,咱們還能夠本身寫進程對象,只需繼承Process類便可:

from multiprocessing import Process
import time
class  ClockProcess(Process):
    # 重寫的run方法就是咱們要執行的子進程方法
    def run(self):
        n = 5
        while n > 0:
            print(n)
            time.sleep(1)
            n -= 1
if __name__ = '__main__':
    p = ClockProcess()
    p.start()
    p.join()

輸出的結果爲:
5
4
3
2
1
上面的內容每隔一秒鐘打印出一個

進程池

進程池用來建立多個進程。

當須要建立的子進程數量很少時,能夠直接利用multiprocessing中的Process動態生成多個進程。但若是咱們須要建立大量的進程,若是手動建立那工做量將極其巨大。並且會產生不少的重複代碼。此時,就能夠用到multiprocessing模塊提供的Pool來實現批量建立多個進程。

初始化Pool時,能夠指定一個最大進程數。當有新的請求提交到Pool中時,若是池尚未滿,那麼就會建立一個新的進程用來執行該請求;若是池中的進程數已經達到指定的最大值,那麼該請求就會等待,直到池中有進程結束,纔會建立新的進程來執行。

建立進程池的基本辦法爲:

from multiprocessing import Pool
import time

def r1():
    print('這裏是進程一呀~')
    time.sleep(5)
def r2():
    print('這裏是進程二呀~')
    time.sleep(3)
if __name__ == '__main__':
    # 定義一個進程池,參數爲最大進程數,默認大小爲CPU核數
    po = Pool()
    for i in range(100):
        # apply_async選擇要調用的目標,每次循環會用空出來的子進程去調用目標
        po.apply_async(r1)
        po.apply_async(r2)
    # 進程池關閉後再也不接收新的請求
    po.close()
    # 等待po中全部子進程結束,必須放在close後面
    po.join()
# 在多進程中,主進程通常用來等待,真正的任務都在子進程中

multiprocessing.Pool經常使用函數解析

  • apply_async(func[, args[, kwargs]]):使用非阻塞方式調用func(並行執行,阻塞方式必須等待上一個進程退出才能執行下一個進程)。args爲傳遞給func的位置參數,kwargs爲傳遞給func 的關鍵字參數
  • apply(func[, args[, kwargs]])(瞭解便可)使用阻塞方式調用func,效果與單進程相同
  • close():關閉進程池,使其再也不接收新的任務
  • join():主進程阻塞,等待子進程的退出,必須在close或terminate以後使用
相關文章
相關標籤/搜索