機器學習之Python基礎(三)--多進程編程
本篇文章主要介紹python的多進程編程,標題如下:
- 多線程與多進程概念
- Python多進程編程之Process
- Python多進程編程之Pool
- Python多進程編程之進程間通信Queue
- Python多進程編程之進程間通信Pipe
- 小結
多線程與多進程概念
在介紹Python多線程編程之前,先給大家複習一下進程和線程的概念。
進程(Process)實際上表示的就是計算機正在進行的一個任務,比如,打開一個瀏覽器便是啟動一個瀏覽器進程,打開一個記事本便是啟動一個記事本進程。
但是,一個進程未必只能進行一件事,就像一個Word進程,在打字的同時還會有拼寫檢查,這些在進程內部同時進行的多個「子任務」,就稱為線程(Thread)。
進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變數,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的並發操作,只能用線程,不能用進程。
在以往的單核CPU上,系統執行多進程的方式是通過不斷的在多個進程中切換——例如任務1執行0.01秒,切到任務2執行0.01秒再切到任務3……以此類推,而在多核CPU出現後,真正的並行執行多任務才真正的得以實現,但繞是如此,一台計算機同時進行的進程是非常之多的,遠遠大於CPU的核心數量,因此,操作系統依然會將這些任務輪流調度到每個核心上運行。
多線程的執行方式類似於多進程,也是通過快速切換來達到看起來「同時運行」的目的
如果我們要同時進行多個任務,我們有以下三種方案:
- 寫多個程序,然後同時運行
- 在一個程序中運行多個線程
- 多進程+多線程
Python多進程編程之Process
多進程的實現與你的操作系統有關。例如Unix/Linux操作系統提供了一個fork()系統調用來創建進程。普通的函數調用,調用一次,返回一次,但是fork()調用一次,返回兩次,因為操作系統自動把當前進程(稱為父進程)複製了一份(稱為子進程),然後,分別在父進程和子進程內返回。子進程永遠返回0,而父進程返回子進程的ID。這樣做的理由是,一個父進程可以fork出很多子進程,所以,父進程要記下每個子進程的ID,而子進程只需要調用getppid()就可以拿到父進程的ID。
而Python的os模塊里正好封裝了該系統調用,所以在Unix/Linux操作系統可以通過os.fork()創建子進程。
但是Window系統是沒有這個系統調用的,因此沒辦法用fork()實現多進程。Python提供了一個multiprocessing模塊來供跨平台版本的Python使用多進程,這個模塊提供了一個Process類來代表一個進程對象。下面是一個啟動子進程並等待其結束的例子:
模塊os提供的方法getpid()可以讓我們查看當前運行的進程的id。創建子進程時,只需要傳入一個執行函數和函數的參數(注意參數args=("child_process",) 後面的那個逗號,因為傳參的方式是傳入元祖,所以如果不加逗號,函數會認為要把 "child_process" 的每個字元都當作參數傳入),創建一個Process實例,然後用start()方法啟動。join()方法表示等待子進程結束後再繼續往下運行,常用於進程間的同步。
有意思的是,如果我們傳參數不按照上述代碼args=()這樣的形式來傳參而是直接類似於target=run("a")這樣的形式傳入參數,就會發現,Python會直接將其作為一個函數放在主進程內運行,而不會再作為子進程單獨運行。
可以看到,若是沒有join()那麼子進程的運行時間就不會和我們預想的同步。
Python多進程編程之Pool
如果我們要創建大量的子進程,可以利用進程池的方式來批量創建子進程。
進程池類Pool同樣是由模塊multiprocess導出
對於Pool對象,若要調用join()則必須提前調用close(),一旦調用close()則無法再添加新的子進程。(如果不調用close(),它會認為你還要添加子進程故無法執行join()
Python多進程編程之進程間通信Queue
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參數來限制隊列里的對象個數)
以下是一個例子,我們創建兩個進程,一個inputer()用於往Queue里寫數據,一個reader()用於從Queue里讀數據
Python多進程編程之進程間通信Pipe
Pipe是方法是實現兩個進程通信的另一種方法。Pipe對象分兩種,一種為單向管道,一種為雙向管道,可以通過構造方法Pipe ( duplex = False ) 來創建單向管道(默認為雙向管道)。
Pipe執行任務的方式是,一個進程從Pipe的一端輸入對象,然後一個進程從Pipe的另一端接收對象,單向管道只允許管道一端的進程輸入,而雙向管道則允許從兩端輸入,下面是一個例子。
調用構造方法Pipe()創建了一個雙向管道,實際上是創建了一個由兩個單向管道組成的二元組,若是一個進程調用了一個單向管道的send方法,那麼另外一個進程就不能再調用這個管道的send方法,我們可以從例子中看到,進程sender用了二元組第一個管道的send,進程recver用了第二個。
小結
掌握Python多進程編程技術可以充分利用多核CPU,極大的提高計算機的執行效率,例如在生成隨機森林的時候,使用多進程可以提高CART的生成速率等等。下期Python基礎將為大家介紹Python的多線程。
本文首發於公眾號「AI遇見機器學習」,更多乾貨可搜索[mltoai]或直接公眾號名,歡迎關注!
推薦閱讀:
※Python數據分析及可視化實例之CentOS7.2+Python3x+Flask部署標準化配置流程
※Flask 實現小說網站 (二)
※Python實現3D建模工具
※Flask模板引擎:Jinja2語法介紹
※OpenCV:圖片操作基本知識(二)