若是隻能用一隻手吃飯+喝湯,吃飯耗時十五分鐘,喝湯五分鐘,這樣確定耗時。人家還想早點吃完開一把LOL呢,那麼很簡單這時候,咱們就會想到左手喝湯,右手吃飯這樣同時進行。這樣時間上吃飯就獲得了優化。python
多線程這個話題天然離不開進程線程這兩個keyword數組
首先簡單介紹下進程:計算機程序只是存儲在磁盤上的可執行二進制(或者其餘類型)文件。從硬盤中讀取他們到內存中這樣才擁有了生命週期。進程或者說重量級進程(同義)是一個執行中的程序,它擁有本身的地址空間、內存、數據棧以及其餘用於跟蹤執行的輔助數據。咱們的操做系統(Operating System,簡稱OS)管理全部進程的執行,併爲這些進程分配合理的地址空間。進程也能夠經過fork或者spawn新的進程來執行其餘任務,新的進程固然也擁有本身的內存和數據棧,因此只能採用進程間通訊(IPC)的方式。網絡
線程或輕量級進程(同義)與進程相似,不過它們是在統一進程下執行的,而且共享相同的上下文,能夠理解一個進程池中的東西線程共享。數據結構
線程包括三部分:開始--執行--結束,指令指針用於記錄當前運行的上下文。當其餘線程運行時,它能夠被搶佔(中斷)和臨時掛起(sleep),這種作法叫作讓步(yielding)。多線程
固然線程中也會有主線程,因此說一個進程中的各個線程與主線程共享一片數據空間。若是一頓午餐至關於一個進程的話,你能夠吃本身的午餐卻很差意思吃別人的午餐(由於吃別人的午餐須要跟他進行溝通(IPC通訊),溝通很差還會被錘!!!),吃本身的想怎麼吃就怎麼吃。線程通常是併發執行的,因爲這種併發和數據共享機制,使得多任務間協做成爲可能。併發
注:在單核的CPU中真正的併發是不可能的,在一段時間內你只可能夾一道菜,吃兩口再夾別的菜,以後也能夠再回頭吃這個菜。 這樣的一個爲所欲爲的夾菜的順序就成了無言的一個排列。線程也是同樣,一個線程運行一段時間而後開始休眠,讓步給其餘的線程(再次排隊等待更多的CPU時間)。在整個進程執行過程當中,每一個線程執行它本身特定的任務,在必要的時和其餘線程進行結果通訊。函數
這樣不免會聯想出問題,若是共享數據,那麼會有風險。oop
若是兩個或多個線程訪問同一片數據,因爲數據訪問順序不一樣,可能致使結果不一致。這種狀況就是「競態條件」。不過大多數線程庫都有一些同步原語,以容許線程管理器控制和訪問。學習
固然啦 吃飯總會有本身愛吃的菜,因此每道菜的攝入量不會公平。也就是說線程沒法給予公平的執行時間。若是沒有專門的多線程狀況進行修改,會致使CPU的時間分配向貪婪函數傾斜。優化
接下來要談論的就是正題:
1.GIL(全局解釋鎖):python代碼的執行是由Python虛擬機(解釋器主循環)進行控制的。在解釋器主循環中只能由一個控制線程在執行,就像單核CPU系統的多進程一個道理,內存中能夠有不少程序,可是在任意給定的時刻只能有一個程序在運行。儘管python解釋器能夠運行多個線程,可是在任意給定時刻只有一個線程會被解釋器執行。
對python虛擬機的訪問是由全局解釋鎖GIL控制的,這個鎖就是用來保證同時只能由一個線程運行。
在python虛擬機中將按照下面的方式來運行。
在Python多線程下,每一個線程的執行方式:
一、獲取GIL
二、執行代碼直到sleep或者是python虛擬機將其掛起。
**三、釋放GIL **
可見,某個線程想要執行,必須先拿到GIL,咱們能夠把GIL看做是「通行證」,而且在一個python進程中,GIL只有一個。拿不到通行證的線程,就不容許進入CPU執行。
在Python2.x裏,GIL的釋放邏輯是當前線程碰見IO操做或者ticks計數達到100(ticks能夠看做是Python自身的一個計數器,專門作用於GIL,每次釋放後歸零,這個計數能夠經過 sys.setcheckinterval 來調整),進行釋放。
而每次釋放GIL鎖,線程進行鎖競爭、切換線程,會消耗資源。而且因爲GIL鎖存在,python裏一個進程永遠只能同時執行一個線程(拿到GIL的線程才能執行),這就是爲何在多核CPU上,python的多線程效率並不高。
那麼是否是python的多線程就徹底沒用了呢?
在這裏咱們進行分類討論:
一、CPU密集型代碼(各類循環處理、計數等等),在這種狀況下,因爲計算工做多,ticks計數很快就會達到閾值,而後觸發GIL的釋放與再競爭(多個線程來回切換固然是須要消耗資源的),因此python下的多線程對CPU密集型代碼並不友好。
二、IO密集型代碼(文件處理、網絡爬蟲等),多線程可以有效提高效率(單線程下有IO操做會進行IO等待,形成沒必要要的時間浪費,而開啓多線程能在線程A等待時,自動切換到線程B,能夠不浪費CPU的資源,從而能提高程序執行效率)。因此python的多線程對IO密集型代碼比較友好。
2.退出線程:
當一個線程完成函數執行的時候,它就會退出。還能夠經過thread.exit()之類的退出函數,或者sys.exit()之類的退出python進程的標準方法,或者拋出SystemExit異常,來使線程退出。可是你不能直接終止一個線程。
主線程:主線程應該是一個好的管理者去管理好調度好好的吃這一頓飯(收集每個線程的結果,生成一個有意義的最終結果。)
3.python中使用線程:
通常來講如今咱們平常使用的電腦都是64位的因此咱們安裝的python解釋器默認都是支持現成的,只須要import好模塊便可使用。
thread:這個模塊不介紹也不推薦使用,緣由是當thread中主線程結束時,全部的其餘線程也都強制結束,不會發出警告或者適當的清理。
threading:經常使用此模塊,由於至少threading模塊能夠確保重要的子線程在進程退出前結束,也成守護線程。
看一個簡單的例子
下面的例子是個basic的例子,沒有使用線程。吃飯使用5s時間喝湯使用2s時間,是串行的,參數loop是循環次數。
結果:
從結果來看喝湯耗時4s,吃飯耗時10s,總共耗時14秒。
加入threading module
結果:
解析:
建立線程數組,用於線程載入,調用threading module的Thread()方法建立線程,而後調用。
結果顯示吃飯喝湯同時進行。start()開始線程活動,join()等待線程終止。若是不使用join()方法對每一個線程作等待終止,那麼在線程運行過程當中會打印吃完XXX。
變強的過程在於思考優化,因此咱們能夠優化線程的建立(線程建立代碼重複冗餘度很高)
那麼咱們能夠集中遍歷建立,而後基於此目的微調下主函數,結果以下:
結果:
有點瑕疵應該在print的部分改成i+1,循環次數也強制寫死爲2次,固然這只是個demo,若是考慮再加個列表讀入times 參數,此時能夠添加判斷是否大於零,能夠本身試一試再也不贅述。
在實際開發中,咱們這種利用線程的方式確定不是咱們經常使用的方式,由於本身在用的時候會傳入不少參數,因此咱們就必須用到繼承Threading,來建立咱們自用的線程類
結果同以前的就不在展現。
慢慢的你會發現學習一部分慢慢的修改,你的代碼也會愈來癒合理,思考的過程就是這樣。
因爲目前咱們的CPU 都是多核的,因此說每個進程中均可以有一個線程在執行在python中,那假如咱們試一試使用多進程,來觀察一下多進程與多線程的區別。
多進程模塊與threading 模塊用法類似,multiprocessing模塊能夠提供本地和遠程的併發性,有效的經過GIL(全局解釋所),來使用進程而不是線程。在多核CPU中使用多線程並不能有效利用CPU的優點。因爲每個CPU中的進程只能在某一時間執行一個線程,因此此時能夠考慮多進程來利用多核CPU,UNIX&WIN都是支持的。
將threading修改爲multiprocessing便可。
結果:注意爲了區別進程號我在結果中加了打印PID number因此顯示結果以下。
這樣能夠明顯區別開。
PID就是相似於身份證的一個東西,用來表示進程。一個進程在結束前都爲一個不變的PID num,可是同一進程能夠有不一樣的PID num,好比多運行兩次此程序你會發現PID num一直在變,或者在本身電腦上運行wechat,反覆開關幾回你也會發現都是不一樣的PID num。
由上面的程序咱們能夠發現,multiprocessing與threading的用法沒什麼大差異,也有start()、run()、join()等方法。
看一下文檔裏面multiprocessing的用法,以下圖。
target表示調用的對象,args表示調用對象的文職參數元組,kwargs表示調用對象的字典,name爲別名,Group基本用不上,爲None也無所謂。
若是去掉PID num,那個從res中徹底看不出multiprocessing與threading有什麼區別。*
因爲線程共享數據,因此不須要通訊,而這點以前也提到過因此,併發進程的狀況下,進程就會須要進程通訊(IPC機制)。
常見支持IPC的類 Queue&Pipe傳送常見的對象。 (1)pipe是單向的,也能夠是雙向的。經過multiprocessing.Pipe(duplex=False)建立單向管道(默認爲雙向,相似於半雙工全雙工的意思,即單爲只容許單向傳輸,雙向則容許雙向傳輸。)
pipe對象默認是雙向的。在其創建的時候,返回一個含有兩個元素的列表,每一個元素表明pipe的一端(Connection對象)。這就成告終果中的對暗號,‘天王蓋地虎,小雞燉蘑菇...不對 寶塔鎮河妖。’
send方法發送,另外一端用recv方法來接收。
(2)Queue類與Pipe相似,不過學過數據結構,你們都知道隊列都是先進先出。Queue容許多個進程放入,多個進程從隊列存取。
這裏最須要搞懂的地方就是這個鎖的用處,就好像把進程們出入的時候帶了手銬不讓亂跑,進一個出一個明確,這樣打印起來就不是很亂。