1. 程式人生 > 其它 >Python多程序和執行緒

Python多程序和執行緒

技術標籤:Pythonpython多執行緒

1、什麼是程序/執行緒

眾所周知,CPU是計算機的核心,它承擔了所有的計算任務。而作業系統是計算機的管理者,是一個大管家,它負責任務的排程,資源的分配和管理,統領整個計算機硬體。應用程式是具有某種功能的程式,程式執行與作業系統之上。

2、程序

程序時一個具有一定功能的程式在一個數據集上的一次動態執行過程。程序由程式,資料集合和程序控制塊三部分組成。程式用於描述程序要完成的功能,是控制程序執行的指令集;資料集合是程式在執行時需要的資料和工作區;程式控制塊(PCB)包含程式的描述資訊和控制資訊,是程序存在的唯一標誌。

3、執行緒

在很早的時候計算機並沒有執行緒這個概念,但是隨著時代的發展,只用程序來處理程式出現很多的不足。如當一個程序堵塞時,整個程式會停止在堵塞處,並且如果頻繁的切換程序,會浪費系統資源。所以執行緒出現了。

執行緒是能擁有資源和獨立執行的最小單位,也是程式執行的最小單位。一個程序可以擁有多個執行緒,而且屬於同一個程序的多個執行緒間會共享該進行的資源。

4、程序與執行緒的區別

  1. 一個程序由一個或者多個執行緒組成,執行緒是一個程序中程式碼的不同執行路線。
  2. 切換程序需要的資源比切換執行緒的要多的多。
  3. 程序之間相互獨立,而同一個程序下的執行緒共享程式的記憶體空間(如程式碼段,資料集,堆疊等)。某程序內的執行緒在其他程序不可見。換言之,執行緒共享同一片記憶體空間,而程序各有獨立的記憶體空間

5、Python多程序和多執行緒選擇

  1. 在以IO操作為主的IO密集型應用中,多執行緒和多程序的效能區別並不大,原因在於即使在Python中有GIL鎖的存在,由於執行緒中的IO操作會使得執行緒立即釋放GIL,切換到其他非IO執行緒繼續操作,提高程式執行效率。相比程序操作,執行緒操作更加輕量級,執行緒之間的通訊複雜度更低,建議使用多執行緒。

  2. 如果是計算密集型的應用,儘量使用多程序或者協程來代替多執行緒。

引用廖雪峰部落格部落格:

因為Python的執行緒雖然是真正的執行緒,但直譯器執行程式碼時,有一個GIL鎖:Global Interpreter Lock,任何Python執行緒執行前,必須先獲得GIL鎖,然後,每執行100條位元組碼,直譯器就自動釋放GIL鎖,讓別的執行緒有機會執行。這個GIL全域性鎖實際上把所有執行緒的執行程式碼都給上了鎖,所以,多執行緒在Python中只能交替執行,即使100個執行緒跑在100核CPU上,也只能用到1個核

下面是引用知乎的講解:

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的直譯器。

難道就如此?我們沒有辦法在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鎖,互不影響。

6、程式碼分析

6.1、計算密集型模擬

  1. 開啟兩個python執行緒分別做一億次加一操作,和單獨使用一個執行緒做一億次加一操作
  2. 開啟兩個python程序分別做一億次加一操作,和單獨使用一個經常做一億次加一操作
import threading
from multiprocessing import Process
import time

def tstart(_str):
    var = 0
    for i in range(100000000):
        var += 1


if __name__ == '__main__':
	t1 = threading.Thread(target=tstart, args=('This is thread 1',))
	t2 = threading.Thread(target=tstart, args=('This is thread 2',))
	start_time = time.time()
	t1.start()
	t1.join()
	t2.start()
	t2.join()
	print("Two thread cost time: %s" % (time.time() - start_time))

	start_time = time.time()
	tstart("This is thread 0")
	print("Main thread cost time: %s" % (time.time() - start_time))
	print("******************************")
	p1 = Process(target=tstart, args=("1",))
	p2 = Process(target=tstart, args=("2",))
	start_time = time.time()
	p1.start()
	p2.start()
	p1.join()
	p2.join()
	print("Two process cost time: %s" % (time.time() - start_time))
	start_time = time.time()
	tstart("0")
	print("Current process cost time: %s" % (time.time() - start_time))

    
# 輸入結果:
Two thread cost time: 7.62561297416687
Main thread cost time: 3.832775831222534
******************************
Two process cost time: 5.084405899047852
Current process cost time: 3.9833498001098633

對比分析

  1. 對應多執行緒來說,由於GIL鎖的存在,採用多執行緒,並不能有效的提升效率

  2. 雙程序並行執行和單程序執行相同運算程式碼,耗時基本相同,雙程序耗時會稍微多一些,可能原因是程序建立和銷燬會進行系統呼叫,造成額外的時間開銷。

參考

知乎,多執行緒