python 併發之執行緒
一.什麼是執行緒
#指的是一條流水線的工作過程,關鍵的一句話:一個程序內最少自帶一個執行緒,其實程序根本不能執行,程序不是執行單位,是資源的單位,分配資源的單位 #執行緒才是執行單位 #程序:做手機螢幕的工作過程,剛才講的 #我們的py檔案在執行的時候,如果你站在資源單位的角度來看,我們稱為一個主程序,如果站在程式碼執行的角度來看,它叫做主執行緒,只是一種形象的說法,其實整個程式碼的執行過程成為執行緒,也就是幹這個活兒的本身稱為執行緒,但是我們後面學習的時候,我們就稱為執行緒去執行某個任務,其實那某個任務的執行過程稱為一個執行緒,一條流水線的執行過程為執行緒 #程序vs執行緒什麼是執行緒#1 同一個程序內的多個執行緒是共享該程序的資源的,不同程序內的執行緒資源肯定是隔離的 #2 建立執行緒的開銷比建立程序的開銷要小的多 #併發三個任務:1啟動三個程序:因為每個程序中有一個執行緒,但是我一個程序中開啟三個執行緒就夠了 #同一個程式中的三個任務需要執行,你是用三個程序好 ,還是三個執行緒好? #例子: # pycharm 三個任務:鍵盤輸入 螢幕輸出 自動儲存到硬碟 #如果三個任務是同步的話,你鍵盤輸入的時候,螢幕看不到 #咱們的pycharm是不是一邊輸入你邊看啊,就是將序列變為了三個併發的任務 #解決方案:三個程序或者三個執行緒,哪個方案可行。如果是三個程序,程序的資源是不是隔離的並且開銷大,最致命的就是資源隔離,但是使用者輸入的資料還要給另外一個程序傳送過去,程序之間能直接給資料嗎?你是不是copy一份給他或者通訊啊,但是資料是同一份,我們有必要搞多個程序嗎,執行緒是不是共享資源的,我們是不是可以使用多執行緒來搞,你執行緒1輸入的資料,執行緒2能不能看到,你以後的場景還是應用多執行緒多,而且起執行緒我們說是不是很快啊,佔用資源也小,還能共享同一個程序的資源,不需要將資料來回的copy!
程序有很多優點,它提供了多道程式設計,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機的利用率。很多人就不理解了,既然程序這麼優秀,為什麼還要執行緒呢?其實,仔細觀察就會發現程序還是有很多缺陷的,主要體現在兩點上:
-
-
程序只能在一個時間幹一件事,如果想同時幹兩件事或多件事,程序就無能為力了。
-
程序在執行的過程中如果阻塞,例如等待輸入,整個程序就會掛起,即使程序中有些工作不依賴於輸入的資料,也將無法執行。
-
如果這兩個缺點理解比較困難的話,舉個現實的例子也許你就清楚了:如果把我們上課的過程看成一個程序的話,那麼我們要做的是耳朵聽老師講課,手上還要記筆記,腦子還要思考問題,這樣才能高效的完成聽課的任務。而如果只提供程序這個機制的話,上面這三件事將不能同時執行,同一時間只能做一件事,聽的時候就不能記筆記,也不能用腦子思考,這是其一;如果老師在黑板上寫演算過程,我們開始記筆記,而老師突然有一步推不下去了,阻塞住了,他在那邊思考著,而我們呢,也不能幹其他事,即使你想趁此時思考一下剛才沒聽懂的一個問題都不行,這是其二。
現在你應該明白了程序的缺陷了,而解決的辦法很簡單,我們完全可以讓聽、寫、思三個獨立的過程,並行起來,這樣很明顯可以提高聽課的效率。而實際的作業系統中,也同樣引入了這種類似的機制——執行緒。
二.執行緒的出現
60年代,在OS中能擁有資源和獨立執行的基本單位是程序,然而隨著計算機技術的發展,程序出現了很多弊端,一是由於程序是資源擁有者,建立、撤消與切換存在較大的時空開銷,因此需要引入輕型程序;二是由於對稱多處理機(SMP)出現,可以滿足多個執行單位,而多個程序並行開銷過大。
因此在80年代,出現了能獨立執行的基本單位——執行緒(Threads)。 注意:程序是資源分配的最小單位,執行緒是CPU排程的最小單位. 每一個程序中至少有一個執行緒。
在傳統作業系統中,每個程序有一個地址空間,而且預設就有一個控制執行緒
執行緒顧名思義,就是一條流水線工作的過程,一條流水線必須屬於一個車間,一個車間的工作過程是一個程序
車間負責把資源整合到一起,是一個資源單位,而一個車間內至少有一個流水線
流水線的工作需要電源,電源就相當於cpu
所以,程序只是用來把資源集中到一起(程序只是一個資源單位,或者說資源集合),而執行緒才是cpu上的執行單位。
多執行緒(即多個控制執行緒)的概念是,在一個程序中存在多個控制執行緒,多個控制執行緒共享該程序的地址空間,相當於一個車間內有多條流水線,都共用一個車間的資源。
例如,北京地鐵與上海地鐵是不同的程序,而北京地鐵裡的13號線是一個執行緒,北京地鐵所有的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。
三.執行緒與程序的關係
執行緒與程序的區別可以歸納為以下4點:
1)地址空間和其它資源(如開啟檔案):程序間相互獨立,同一程序的各執行緒間共享。某程序內的執行緒在其它程序不可見。 2)通訊:程序間通訊IPC,執行緒間可以直接讀寫程序資料段(如全域性變數)來進行通訊——需要程序同步和互斥手段的輔助,以保證資料的一致性。(就類似程序中的鎖的作用) 3)排程和切換:執行緒上下文切換比程序上下文切換要快得多。 4)在多執行緒作業系統中(現在咱們用的系統基本都是多執行緒的作業系統),程序不是一個可執行的實體,真正去執行程式的不是程序,是執行緒,你可以理解程序就是一個執行緒的容器 四.執行緒的特點 先簡單瞭解一下執行緒有哪些特點,裡面的堆疊啊主存區啊什麼的後面會講,大家先大概瞭解一下就好啦。 在多執行緒的作業系統中,通常是在一個程序中包括多個執行緒,每個執行緒都是作為利用CPU的基本單位,是花費最小開銷的實體。執行緒具有以下屬性。 1)輕型實體 執行緒中的實體基本上不擁有系統資源,只是有一些必不可少的、能保證獨立執行的資源。 執行緒的實體包括程式、資料和TCB。執行緒是動態概念,它的動態特性由執行緒控制塊TCB(Thread Control Block)描述。TCB包括以下資訊: (1)執行緒狀態。 (2)當執行緒不執行時,被儲存的現場資源。 (3)一組執行堆疊。 (4)存放每個執行緒的區域性變數主存區。 (5)訪問同一個程序中的主存和其它資源。 用於指示被執行指令序列的程式計數器、保留區域性變數、少數狀態引數和返回地址等的一組暫存器和堆疊。
2)獨立排程和分派的基本單位。
在多執行緒OS中,執行緒是能獨立執行的基本單位,因而也是獨立排程和分派的基本單位。由於執行緒很“輕”,故執行緒的切換非常迅速且開銷小(在同一程序中的)。 3)共享程序資源。 執行緒在同一程序中的各個執行緒,都可以共享該程序所擁有的資源,這首先表現在:所有執行緒都具有相同的程序id,這意味著,執行緒可以訪問該程序的每一個記憶體資源;此外,還可以訪問程序所擁有的已開啟檔案、定時器、訊號量機構等。由於同一個程序內的執行緒共享記憶體和檔案,所以執行緒之間互相通訊不必呼叫核心。 4)可併發執行。 在一個程序中的多個執行緒之間,可以併發執行,甚至允許在一個程序中所有執行緒都能併發執行;同樣,不同程序中的執行緒也能併發執行,充分利用和發揮了處理機與外圍裝置並行工作的能力。 五.執行緒的建立 1.兩種方式1 from threading import Thread 2 # def f1(n): 3 # print('xx%s'%n) 4 # 5 # 6 # def f2(n): 7 # print('ss%s'%n) 8 # 9 # 10 # if __name__ == '__main__': 11 # t = Thread(target=f1,args=(1,)) 12 # t1 = Thread(target=f2,args=(2,)) 13 # t.start() 14 # t1.start() 15 16 # 第二種建立方式 17 class Mythread(Thread): 18 19 20 def run(self): 21 print('哈哈哈') 22 23 24 if __name__ == '__main__': 25 t = Mythread() 26 t.start()
六.多程序和多執行緒的效率對比
1 import time 2 from threading import Thread 3 from multiprocessing import Process 4 5 def f1(): 6 # time.sleep(1) # io密集型 有阻塞 7 # 計算型: 無阻塞 8 n = 10 9 for i in range(10000000): 10 n = n + i 11 12 if __name__ == '__main__': 13 # 檢視一下20個執行緒執行20個任務的執行時間 14 t_s_time = time.time() 15 t_list = [] 16 for i in range(20): 17 t =Thread(target=f1,) 18 t.start() 19 t_list.append(t) 20 21 [tt.join() for tt in t_list] 22 23 t_e_time = time.time() 24 25 t_dif_time = t_e_time - t_s_time 26 # 檢視一下20個程序執行同樣的任務的執行時間 27 p_s_time = time.time() 28 p_list = [] 29 for i in range(20): 30 p = Process(target=f1,) 31 p.start() 32 p_list.append(p) 33 34 [pp.join() for pp in p_list] 35 36 p_e_time = time.time() 37 38 p_dif_time = p_e_time - p_s_time 39 40 print('多執行緒時間:',t_dif_time) 41 print('多程序時間:',p_dif_time) 42 43 44 # 計算型 多執行緒比多程序費時間 45 # io密集型 多執行緒比多程序省時間
七.執行緒鎖
1.鎖
1 import time 2 from threading import Lock,Thread 3 4 num = 100 5 def f1(loc): 6 loc.acquire() 7 global num 8 tmp = num 9 tmp -= 1 10 time.sleep(0.00001) 11 num = tmp 12 loc.release() 13 14 if __name__ == '__main__': 15 t_loc = Lock() 16 t_list = [] 17 for i in range(10): 18 t = Thread(target=f1,args=(t_loc,)) 19 t.start() 20 t_list.append(t) 21 [tt.join() for tt in t_list] 22 print('主線成的num:',num)
2.死鎖
程序也有死鎖與遞迴鎖,在程序那裡忘記說了,放到這裡一切說了額,程序的死鎖和執行緒的是一樣的,而且一般情況下程序之間是資料不共享的,不需要加鎖,由於執行緒是對全域性的資料共享的,所以對於全域性的資料進行操作的時候,要加鎖。
所謂死鎖: 是指兩個或兩個以上的程序或執行緒在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序,如下就是死鎖
import time from threading import Thread,Lock def f1(locA,locB): locA.acquire() print('f1>>>1號搶到了A鎖') time.sleep(1) locB.acquire() print('f1>>>1號搶到了B鎖') locB.release() locA.release() def f2(locA,locB): locB.acquire() print('f2>>>2號搶到了A鎖') time.sleep(1) locA.acquire() print('f2>>>2號搶到了B鎖') locA.release() locB.release() if __name__ == '__main__': locA = locB = Lock() t1 = Thread(target=f1,args=(locA,locB)) t2 = Thread(target=f2,args=(locA,locB)) t1.start() t2.start()
解決方案使用遞迴鎖
1 import time 2 from threading import Thread,RLock 3 4 def f1(locA,locB): 5 locA.acquire() 6 print('f1>>>1號搶到了A鎖') 7 time.sleep(1) 8 locB.acquire() 9 print('f1>>>1號搶到了B鎖') 10 locB.release() 11 locA.release() 12 def f2(locA,locB): 13 locB.acquire() 14 print('f2>>>2號搶到了A鎖') 15 time.sleep(1) 16 locA.acquire() 17 # time.sleep(1) 18 print('f2>>>2號搶到了B鎖') 19 locA.release() 20 locB.release() 21 22 if __name__ == '__main__': 23 locA = locB = RLock() 24 25 #locA = locB = RLock() # 遞迴鎖,維護一個計數器,acquire一次就加1,release就減1 26 t1 = Thread(target=f1,args=(locA,locB)) 27 t2 = Thread(target=f2,args=(locA,locB)) 28 t1.start() 29 t2.start()
3.GIL鎖
八.守護執行緒
1 import time 2 from threading import Thread 3 4 def f1(): 5 time.sleep(2) 6 print('1號執行緒') 7 8 def f2(): 9 time.sleep(3) 10 print('2號執行緒') 11 12 13 if __name__ == '__main__': 14 t1 = Thread(target=f1,) 15 t2 = Thread(target=f2,) 16 t1.daemon = True 17 # t2.daemon = True 18 t1.start() 19 t2.start() 20 print('主執行緒結束')
守護執行緒or守護程序區別
1 import time 2 from threading import Thread 3 from multiprocessing import Process 4 5 # 守護程序: 主程序程式碼執行執行結束,守護程序隨之結束 6 7 # 守護執行緒:守護執行緒會等待所有非守護執行緒執行結束才結束 8 9 def f1(s): 10 time.sleep(2) 11 print('1號%s'%s) 12 13 def f2(s): 14 time.sleep(3) 15 print('2號%s'%s) 16 17 if __name__ == '__main__': 18 # 多執行緒 19 # t1 = Thread(target=f1,args=('執行緒',)) 20 # t2 = Thread(target=f2,args=('執行緒',)) 21 # t1.daemon = True # 守護執行緒 22 # # t2.daemon = True # 守護執行緒 23 # t1.start() 24 # t2.start() 25 # print('主執行緒結束') 26 27 # 多程序 28 t1 = Process(target=f1,args=('程序',)) 29 t2 = Process(target=f2,args=('程序',)) 30 # t1.daemon = True 31 t2.daemon = True 32 t1.start() 33 t2.start() 34 print('主程序結束')
守護執行緒:等待所有非守護執行緒的結束才結束
守護程序:主程序程式碼執行結束,守護程序就隨之結束