1. 程式人生 > >python 併發之執行緒

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('主程序結束')

 

 

 

 

 

 

守護執行緒:等待所有非守護執行緒的結束才結束

守護程序:主程序程式碼執行結束,守護程序就隨之結束