1. 程式人生 > >python 並發之線程

python 並發之線程

序列 觀察 mon -s range copy 狀態 div join

一.什麽是線程

技術分享圖片
#指的是一條流水線的工作過程,關鍵的一句話:一個進程內最少自帶一個線程,其實進程根本不能執行,進程不是執行單位,是資源的單位,分配資源的單位
#線程才是執行單位
#進程:做手機屏幕的工作過程,剛才講的
#我們的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 並發之線程