1. 程式人生 > >執行緒的建立 驗證執行緒之間共享資料 守護執行緒 執行緒程序效率對比 鎖 死鎖 遞迴鎖

執行緒的建立 驗證執行緒之間共享資料 守護執行緒 執行緒程序效率對比 鎖 死鎖 遞迴鎖

執行緒(from threading import Thread):CPU排程的最小單位
執行緒的兩種建立方式:
方式一:
1 from threading import Thread
2 def f1(i):
3     print(i)
4 if __name__ == '__main__':
5     for i in range(10):
6         t = Thread(target=f1,args=(i,))
7         t.start()
8     print('主執行緒')

方式二:

 1 from threading import Thread
2 class MyThread(Thread): 3 def __init__(self, i): 4 super().__init__() 5 self.i = i 6 7 def run(self): 8 print('%s哈哈' % self.i) 9 10 if __name__ == '__main__': 11 for i in range(10): 12 t = MyThread(i) 13 t.start() 14 print('主執行緒')

執行緒之間資料共享的驗證:

 1 import time
 2 from threading import Thread
 3 num = 100
 4 def f1():
 5     global num
 6     num = 33
 7     time.sleep(2)
 8     print('num>>>',num)
 9 if __name__ == '__main__':
10     t = Thread(target=f1,)
11     t.start()
12     print('主執行緒的num',num)
多執行緒跟多程序的效率對比:
  
結論:當遇到io阻塞情況,多執行緒比多程序效率高,當遇到大量計算情況,多程序比多執行緒效率高
 1 import time
 2 from threading import Thread
 3 from multiprocessing import Process
 4 def f1():
 5     # time.sleep(1)
 6     n = 10
 7     for i in range(10000000):
 8         n += i
 9 if __name__ == '__main__':
10     t_s_time = time.time()
11     t_list = []
12     for i in range(10):
13         t = Thread(target=f1,)
14         t.start()
15         t_list.append(t)
16     [tt.join() for tt in t_list]
17     t_e_time = time.time()
18     t_dif_time = t_e_time - t_s_time
19 
20     p_s_time = time.time()
21     p_list = []
22     for i in range(10):
23         p = Process(target=f1,)
24         p.start()
25         p_list.append(p)
26     [pp.join() for pp in p_list]
27     p_e_time = time.time()
28     p_dif_time = p_e_time - p_s_time
29     print('執行緒的時間:',t_dif_time)
30     print('程序的時間:',p_dif_time)
執行緒鎖(互斥鎖):
作用:犧牲了效率,保證了資料安全
 1 import time
 2 from threading import Thread,Lock
 3 num = 100
 4 def f1(t_lock):
 5     t_lock.acquire()
 6     global num
 7     tme = num
 8     tme -= 1
 9     num = tme
10     time.sleep(1)
11     print('子執行緒的num',num)
12     t_lock.release()
13 if __name__ == '__main__':
14     t_lock = Lock()
15     t_list = []
16     for i in range(10):
17         t = Thread(target=f1,args=(t_lock,))
18         t.start()
19         t_list.append(t)
20     [tt.join() for tt in t_list]
21     print('主執行緒的num',num)
死鎖現象:出現在鎖巢狀的時候,雙方互相搶對方已經拿到的鎖,導致雙方互相等待,程式卡住(重點)
死鎖模擬:
 1 import time
 2 from threading import Thread,Lock
 3 def f1(t_lockA,t_lockB):
 4     t_lockA.acquire()
 5     print('f1搶到了A鎖')
 6     time.sleep(1)
 7     t_lockB.acquire()
 8     print('f1搶到了B鎖')
 9     t_lockB.release()
10     t_lockA.release()
11 def f2(t_lockA,t_lockB):
12     t_lockB.acquire()
13     print('f2搶到了B鎖')
14     time.sleep(1)
15     t_lockA.acquire()
16     print('f2搶到了A鎖')
17     t_lockA.release()
18     t_lockB.release()
19 if __name__ == '__main__':
20     t_lockA = Lock()
21     t_lockB = Lock()
22     t1 = Thread(target=f1,args=(t_lockA,t_lockB))
23     t2 = Thread(target=f2,args=(t_lockA,t_lockB))
24     t1.start()
25     t2.start()
遞迴鎖:最常用的執行緒鎖,解決了死鎖現象
  Rlock  首先本身就是個互斥鎖,維護了一個計數器,每次acquire就+1,release就-1,當計數器為0的時候,大家才能搶這個鎖
 1  import time
 2         from threading import Thread,RLock
 3         def f1(lokA,lokB):
 4             lokA.acquire()
 5             print('f1搶到了A鎖')
 6             time.sleep(1)
 7             lokB.acquire()
 8             print('f1搶到B鎖了')
 9             lokB.release()
10             lokA.release()
11         def f2(lokA,lokB):
12             lokB.acquire()
13             print('f2搶到了B鎖')
14             time.sleep(1)
15             lokA.acquire()
16             print('f2搶到了A鎖')
17             lokA.release()
18             lokB.release()
19         if __name__ == '__main__':
20             lokA = lokB = RLock() #遞迴鎖,維護一個計數器,acquire一次就加1,release就減1
21             t1 = Thread(target=f1,args=(lokA,lokB))
22             t2 = Thread(target=f2,args=(lokA,lokB))
23             t1.start()
24             t2.start()
多執行緒守護跟多程序守護的區別:
區別:
守護程序:主程序程式碼執行執行結束,守護程序隨之結束
守護執行緒:守護執行緒會等待所有非守護執行緒執行結束才結束
程式碼示例:
 1 import time
 2             from threading import Thread
 3             from multiprocessing import Process
 4             def f1():
 5                 time.sleep(2)
 6                 print('一號')
 7             def f2():
 8                 time.sleep(3)
 9                 print('二號')
10             if __name__ == '__main__':
11                 t1 = Thread(target=f1,)
12                 t2 = Thread(target=f2,)
13                 t1.daemon = True
14                 t2.daemon = True #守護執行緒:守護執行緒會等待所有非守護執行緒執行結束才結束
15                 t1.start()
16                 t2.start()
17                 print('執行緒結束')
18                 p1 = Process(target=f1,)
19                 p2 = Process(target=f2,)
20                 p1.daemon = True
21                 p2.daemon = True #守護程序:主程序程式碼執行執行結束,守護程序隨之結束
22                 p1.start()
23                 p2.start()
24                 print('程序結束')
GIL鎖 : cpython直譯器上的一把互斥鎖
    首先需要明確的一點是GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(語法)標準,但是可以用不同的編譯器來編譯成
  可執行程式碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段程式碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行。
  像其中的JPython就沒有GIL。然而因為CPython是大部分環境下預設的Python執行環境。所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷。
  所以這裡要先明確一點:GIL並不是Python的特性,Python完全可以不依賴於GIL

  GIL本質就是一把互斥鎖,既然是互斥鎖,所有互斥鎖的本質都一樣,都是將併發執行變成序列,以此來控制同一時間內共享資料只能被一個任務所修改,進而保證資料安全。

    可以肯定的一點是:保護不同的資料的安全,就應該加不同的鎖。

    要想了解GIL,首先確定一點:每次執行python程式,都會產生一個獨立的程序。例如python test.py,python aaa.py,python bbb.py會產生3個不同的python程序

 1 '''
 2 #驗證python test.py只會產生一個程序
 3 #test.py內容
 4 import os,time
 5 print(os.getpid())
 6 time.sleep(1000)
 7 '''
 8 python3 test.py 
 9 #在windows下
10 tasklist |findstr python
11 #在linux下
12 ps aux |grep python

 

如果多個執行緒的target=work,那麼執行流程是

  多個執行緒先訪問到直譯器的程式碼,即拿到執行許可權,然後將target的程式碼交給直譯器的程式碼去執行

  直譯器的程式碼是所有執行緒共享的,所以垃圾回收執行緒也可能訪問到直譯器的程式碼而去執行,這就導致了一個問題:對於同一個資料100,可能執行緒1執行x=100的同時,而垃圾回收執行的是回收100的操作,解決這種問題沒有什麼高明的方法,就是加鎖處理,如下圖的GIL,保證python直譯器同一時間只能執行一個任務的程式碼

 

 

GIL保護的是直譯器級的資料,保護使用者自己的資料則需要自己加鎖處理,如下圖

 

結論:

    對計算來說,cpu越多越好,但是對於I/O來說,再多的cpu也沒用

    當然對執行一個程式來說,隨著cpu的增多執行效率肯定會有所提高(不管提高幅度多大,總會有所提高),這是因為一個程式基本上不會是純計算或者純I/O,所以我們只能相對的去看一個程式到底是計算密集型還是I/O密集型,從而進一步分析python的多執行緒到底有無用武之地

#分析:
我們有四個任務需要處理,處理方式肯定是要玩出併發的效果,解決方案可以是:
方案一:開啟四個程序
方案二:一個程序下,開啟四個執行緒

#單核情況下,分析結果: 
  如果四個任務是計算密集型,沒有多核來平行計算,方案一徒增了建立程序的開銷,方案二勝
  如果四個任務是I/O密集型,方案一建立程序的開銷大,且程序的切換速度遠不如執行緒,方案二勝

#多核情況下,分析結果:
  如果四個任務是計算密集型,多核意味著平行計算,在python中一個程序中同一時刻只有一個執行緒執行用不上多核,方案一勝
  如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝

 
#結論:現在的計算機基本上都是多核,python對於計算密集型的任務開多執行緒的效率並不能帶來多大效能上的提升,甚至不如序列(沒有大量切換),但是,對於IO密集型的任務效率還是有顯著提升的。

計算密集型:多程序效率高

 1 from multiprocessing import Process
 2 from threading import Thread
 3 import os,time
 4 def work():
 5     res=0
 6     for i in range(100000000):
 7         res*=i
 8 
 9 
10 if __name__ == '__main__':
11     l=[]
12     print(os.cpu_count()) #本機為4核
13     start=time.time()
14     for i in range(4):
15         p=Process(target=work) #耗時5s多
16         p=Thread(target=work) #耗時18s多
17         l.append(p)
18         p.start()
19     for p in l:
20         p.join()
21     stop=time.time()
22     print('run time is %s' %(stop-start))

I/O密集型:多執行緒效率高

 1 from multiprocessing import Process
 2 from threading import Thread
 3 import threading
 4 import os,time
 5 def work():
 6     time.sleep(2)
 7     print('===>')
 8 
 9 if __name__ == '__main__':
10     l=[]
11     print(os.cpu_count()) #本機為4核
12     start=time.time()
13     for i in range(400):
14         # p=Process(target=work) #耗時12s多,大部分時間耗費在建立程序上
15         p=Thread(target=work) #耗時2s多
16         l.append(p)
17         p.start()
18     for p in l:
19         p.join()
20     stop=time.time()
21     print('run time is %s' %(stop-start))

 應用:

    多執行緒用於IO密集型,如socket,爬蟲,web
    多程序用於計算密集型,如金融分析