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(‘主進程結束‘)
守護線程:等待所有非守護線程的結束才結束
守護進程:主進程代碼運行結束,守護進程就隨之結束
python 並發之線程