Python中多線程和多進程詳解

一、多線程與多進程

開始之前先來介紹一下何為進程?進程(Process)實際上表示的就是計算機正在進行的一個任務,比如,打開一個瀏覽器便是啟動一個瀏覽器進程,打開一個記事本便是啟動一個記事本進程。

但是,一個進程未必只能進行一件事,就像一個Word進程,在打字的同時還會有拼寫檢查,這些在進程內部同時進行的多個“子任務”,就稱為線程(Thread)。

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

在以往的單核CPU上,系統執行多進程的方式是通過不斷的在多個進程中切換——例如任務1執行0.01秒,切到任務2執行0.01秒再切到任務3……以此類推,而在多核CPU出現後,真正的並行執行多任務才真正的得以實現,但繞是如此,一臺計算機同時進行的進程是非常之多的,遠遠大於CPU的核心數量,因此,操作系統依然會將這些任務輪流調度到每個核心上運行。

多線程的執行方式類似於多進程,也是通過快速切換來達到看起來“同時運行”的目的

如果我們要同時進行多個任務,我們有以下三種方案:

  1. 寫多個程序,然後同時運行
  2. 在一個程序中運行多個線程
  3. 多進程+多線程

二、Python多進程編程

多進程:

多進程的實現與你的操作系統有關。例如Unix/Linux操作系統提供了一個fork()系統調用來創建進程。普通的函數調用,調用一次,返回一次,但是fork()調用一次,返回兩次,因為操作系統自動把當前進程(稱為父進程)複製了一份(稱為子進程),然後,分別在父進程和子進程內返回。

但是Window系統是沒有這個系統調用的,因此沒辦法用fork()實現多進程。Python提供了一個multiprocessing模塊來供跨平臺版本的Python使用多進程,這個模塊提供了一個Process類來代表一個進程對象。

進程池:

如果我們要創建大量的子進程,可以利用進程池的方式來批量創建子進程。

進程池類Pool同樣是由模塊multiprocess導出。

對於Pool對象,若要調用join()則必須提前調用close(),一旦調用close()則無法再添加新的子進程。(如果不調用close(),它會認為你還要添加子進程故無法執行join() )

三、進程間通信

Python模塊multiprcess提供Queue和Pipe類來進行進程間的通信,另外還有很多方式,這裡我們先介紹提出的這兩種。

Queue是多進程安全的隊列,可以使用Queue實現多進程之間的數據傳遞。Queue通過put()方法把數據插入到隊尾,get()方法用於從隊頭取出數據。並且它們都有兩個參數分別為blocked和timeout。當隊列已滿且blocked為True的時候,如果timeout為正值,則會阻塞timeout指定的時間,直到該隊列有剩餘的空間。如果超時,會拋出Queue.Full異常,同理當隊列為空且blocked為True的時候,如果timeout為正值,則會等待timeout時間直到有數據插入再取走。若等待時間內沒有型數據插入則會拋出Queue.Empty異常。(創建Queue對象時接受一個maxsize參數來限制隊列裡的對象個數)。

Pipe是方法是實現兩個進程通信的另一種方法。Pipe對象分兩種,一種為單向管道,一種為雙向管道,可以通過構造方法Pipe ( duplex = False ) 來創建單向管道(默認為雙向管道)。

Pipe執行任務的方式是,一個進程從Pipe的一端輸入對象,然後一個進程從Pipe的另一端接收對象,單向管道只允許管道一端的進程輸入,而雙向管道則允許從兩端輸。

調用構造方法Pipe()創建了一個雙向管道,實際上是創建了一個由兩個單向管道組成的二元組,若是一個進程調用了一個單向管道的send方法,那麼另外一個進程就不能再調用這個管道的send方法,我們可以從例子中看到,進程sender用了二元組第一個管道的send,進程recver用了第二個。

四、小結

掌握Python多進程編程技術可以充分利用多核CPU,極大的提高計算機的執行效率,例如在生成隨機森林的時候,使用多進程可以提高CART的生成速率等等。


分享到:


相關文章: