來源知乎:https://www.zhihu.com/question/23474039/answer/269526476網絡
在介紹Python中的線程以前,先明確一個問題,Python中的多線程是假的多線程! 爲何這麼說,咱們先明確一個概念,全局解釋器鎖(GIL)。Python代碼的執行由Python虛擬機(解釋器)來控制。Python在設計之初就考慮要在主循環中,同時只有一個線程在執行,就像單CPU的系統中運行多個進程那樣,內存中能夠存放多個程序,但任意時刻,只有一個程序在CPU中運行。一樣地,雖然Python解釋器能夠運行多個線程,只有一個線程在解釋器中運行。對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同時只有一個線程在運行。在多線程環境中,Python虛擬機按照如下方式執行。1.設置GIL。2.切換到一個線程去執行。3.運行。4.把線程設置爲睡眠狀態。5.解鎖GIL。6.再次重複以上步驟。對全部面向I/O的(會調用內建的操做系統C代碼的)程序來講,GIL會在這個I/O調用以前被釋放,以容許其餘線程在這個線程等待I/O的時候運行。若是某線程並未使用不少I/O操做,它會在本身的時間片內一直佔用處理器和GIL。也就是說,I/O密集型的Python程序比計算密集型的Python程序更能充分利用多線程的好處。咱們都知道,比方我有一個4核的CPU,那麼這樣一來,在單位時間內每一個核只能跑一個線程,而後時間片輪轉切換。可是Python不同,它無論你有幾個核,單位時間多個核只能跑一個線程,而後時間片輪轉。看起來很難以想象?可是這就是GIL搞的鬼。任何Python線程執行前,必須先得到GIL鎖,而後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把全部線程的執行代碼都給上了鎖,因此,多線程在Python中只能交替執行,即便100個線程跑在100核CPU上,也只能用到1個核。一般咱們用的解釋器是官方實現的CPython,要真正利用多核,除非重寫一個不帶GIL的解釋器。多線程
咱們不妨作個試驗:ide
#coding=utf-8 from multiprocessing import Pool from threading import Thread from multiprocessing import Process def loop(): while True: pass if __name__ == '__main__': for i in range(3): t = Thread(target=loop) t.start() while True: pass
個人電腦是4核,因此我開了4個線程,看一下CPU資源佔有率:oop
咱們發現CPU利用率並無佔滿,大體至關於單核水平。spa
而若是咱們變成進程呢?操作系統
咱們改一下代碼:線程
#coding=utf-8 from multiprocessing import Pool from threading import Thread from multiprocessing import Process def loop(): while True: pass if __name__ == '__main__': for i in range(3): t = Process(target=loop) t.start() while True: pass
結果直接飆到了100%,說明進程是能夠利用多核的!設計
爲了驗證這是Python中的GIL搞得鬼,我試着用Java寫相同的代碼,開啓線程,咱們觀察一下:3d
package com.darrenchan.thread; public class TestThread { public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new Runnable() { @Override public void run() { while (true) { } } }).start(); } while(true){ } } }
因而可知,Java中的多線程是能夠利用多核的,這是真正的多線程!而Python中的多線程只能利用單核,這是假的多線程!code
難道就如此?咱們沒有辦法在Python中利用多核?固然能夠!剛纔的多進程算是一種解決方案,還有一種就是調用C語言的連接庫。對全部面向I/O的(會調用內建的操做系統C代碼的)程序來講,GIL會在這個I/O調用以前被釋放,以容許其餘線程在這個線程等待I/O的時候運行。咱們能夠把一些 計算密集型任務用C語言編寫,而後把.so連接庫內容加載到Python中,由於執行C代碼,GIL鎖會釋放,這樣一來,就能夠作到每一個核都跑一個線程的目的!
可能有的小夥伴不太理解什麼是計算密集型任務,什麼是I/O密集型任務?
計算密集型任務的特色是要進行大量的計算,消耗CPU資源,好比計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。這種計算密集型任務雖然也能夠用多任務完成,可是任務越多,花在任務切換的時間就越多,CPU執行任務的效率就越低,因此,要最高效地利用CPU,計算密集型任務同時進行的數量應當等於CPU的核心數。
計算密集型任務因爲主要消耗CPU資源,所以,代碼運行效率相當重要。Python這樣的腳本語言運行效率很低,徹底不適合計算密集型任務。對於計算密集型任務,最好用C語言編寫。
第二種任務的類型是IO密集型,涉及到網絡、磁盤IO的任務都是IO密集型任務,這類任務的特色是CPU消耗不多,任務的大部分時間都在等待IO操做完成(由於IO的速度遠遠低於CPU和內存的速度)。對於IO密集型任務,任務越多,CPU效率越高,但也有一個限度。常見的大部分任務都是IO密集型任務,好比Web應用。
IO密集型任務執行期間,99%的時間都花在IO上,花在CPU上的時間不多,所以,用運行速度極快的C語言替換用Python這樣運行速度極低的腳本語言,徹底沒法提高運行效率。對於IO密集型任務,最合適的語言就是開發效率最高(代碼量最少)的語言,腳本語言是首選,C語言最差。
綜上,Python多線程至關於單核多線程,多線程有兩個好處:CPU並行,IO並行,單核多線程至關於自斷一臂。因此,在Python中,可使用多線程,但不要期望能有效利用多核。若是必定要經過多線程利用多核,那隻能經過C擴展來實現,不過這樣就失去了Python簡單易用的特色。不過,也不用過於擔憂,Python雖然不能利用多線程實現多核任務,但能夠經過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響。