【目錄】
一、 threading模塊介紹
二 、開啟執行緒的兩種方式
三 、在一個行程下開啟多個執行緒與在一個行程下開啟多個子行程的區別
四、 執行緒相關的其他方法
五、守護執行緒
六、Python GIL(Global Interpreter Lock)
八、同步鎖
九、死鎖現象與遞回鎖
十、執行緒queue
一、 threading模塊介紹
行程的multiprocess模塊,完全模仿了threading模塊的介面,二者在使用層面,有很大的相似性,此處省略好多字,,
multiprocess模塊用法回顧:https://www.cnblogs.com/bigorangecc/p/12759151.html
官網鏈接:https://docs.python.org/3/library/threading.html?highlight=threading#
二 、開啟執行緒的兩種方式
1、實體化類創建物件
#方式一 from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.start() print('主執行緒')
2、類的繼承
#方式二 from threading import Thread import time class Sayhi(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): time.sleep(2) print('%s say hello' % self.name) if __name__ == '__main__': t = Sayhi('egon') t.start() print('主執行緒')
三 、在一個行程下開啟多個執行緒與在一個行程下開啟多個子行程的區別
1、比較開啟速度
from threading import Thread from multiprocessing import Process import os def work(): print('hello') if __name__ == '__main__': #在主行程下開啟執行緒 t=Thread(target=work) t.start() print('主執行緒/主行程') ''' 列印結果: hello 主執行緒/主行程 ''' #在主行程下開啟子行程 t=Process(target=work) t.start() print('主執行緒/主行程') ''' 列印結果: 主執行緒/主行程 hello '''誰的開啟速度快
2、pid
from threading import Thread from multiprocessing import Process import os def work(): print('hello',os.getpid()) if __name__ == '__main__': #part1:在主行程下開啟多個執行緒,每個執行緒都跟主行程的pid一樣 t1=Thread(target=work) t2=Thread(target=work) t1.start() t2.start() print('主執行緒/主行程pid',os.getpid()) #part2:開多個行程,每個行程都有不同的pid p1=Process(target=work) p2=Process(target=work) p1.start() p2.start() print('主執行緒/主行程pid',os.getpid())瞅一瞅pid
3、同一行程內的執行緒之間共享行程內的資料
from threading import Thread from multiprocessing import Process import os def work(): global n n=0 if __name__ == '__main__': # n=100 # p=Process(target=work) # p.start() # p.join() # print('主',n) #毫無疑問子行程p已經將自己的全域的n改成了0,但改的僅僅是它自己的,查看父行程的n仍然為100 n=1 t=Thread(target=work) t.start() t.join() print('主',n) #查看結果為0,因為同一行程內的執行緒之間共享行程內的資料驗證
四、 執行緒相關的其他方法
1、thread實體物件的方法:
1、isAlive(): 回傳執行緒是否活動的,存活的,
2、getName(): 回傳執行緒名,
3、setName(): 設定執行緒名,
2、threading模塊提供的一些方法:
1、threading.currentThread()
回傳當前的執行緒變數,
2、threading.enumerate()
回傳一個包含正在運行的執行緒的list,正在運行指執行緒啟動后、結束前,不包括啟動前和終止后的執行緒,
3、threading.activeCount()
回傳正在運行的執行緒數量,與 len(threading.enumerate()) 有相同的結果,
from threading import Thread import threading from multiprocessing import Process import os def work(): import time time.sleep(3) print(threading.current_thread().getName()) if __name__ == '__main__': #在主行程下開啟執行緒 t=Thread(target=work) t.start() print(threading.current_thread().getName()) print(threading.current_thread()) #主執行緒 print(threading.enumerate()) #連同主執行緒在內有兩個運行的執行緒 print(threading.active_count()) print('主執行緒/主行程') ''' 列印結果: MainThread <_MainThread(MainThread, started 140735268892672)> [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>] 主執行緒/主行程 Thread-1 '''應用舉例
3、join()的使用——主執行緒等待子執行緒結束
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.start() t.join() print('主執行緒') print(t.is_alive()) ''' egon say hello 主執行緒 False '''栗子
五、守護執行緒
1、強調
無論是行程還是執行緒,都遵循:守護xxx會等待主xxx運行完畢后被銷毀
需要強調的是:運行完畢(真正的任務已經做完,但還沒有下班) 并非 終止運行(任務已經完成,收拾好東西下班了)
#1.對主行程來說,運行完畢指的是主行程代碼運行完畢
#2.對主執行緒來說,運行完畢指的是主執行緒所在的行程內所有非守護執行緒統統運行完畢,主執行緒才算運行完畢
詳細解釋:
#1 主行程在其代碼結束后就已經算運行完畢了(守護行程在此時就被回收),
然后主行程會一直等非守護的子行程都運行完畢后回收子行程的資源(否則會產生僵尸行程),才會結束運行,
#2 主執行緒在其他非守護執行緒運行完畢后才算運行完畢(守護執行緒在此時就被回收),
因為主執行緒的結束意味著行程的結束,行程整體的資源都將被回收,而行程必須保證非守護執行緒都運行完畢后才能結束,
2、兩顆栗子
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.setDaemon(True) #必須在t.start()之前設定 t.start() print('主執行緒') print(t.is_alive()) ''' 主執行緒 True '''栗子1
from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------")栗子2——迷人版
六、Python GIL (Global Interpreter Lock)
1、強調
# 在Cpython解釋器中,同一個行程下開啟的多執行緒,同一時刻只能有一個執行緒執行,無法利用多核優勢.
# GIL 并不是python的特性,它是在實作python解釋器(cpython)時所引入的一個概念,
即 GIL 是 python解釋器(cpython)的特性,
2、GIL
GIL 本質就是一把互斥鎖,既然是互斥鎖,所有互斥鎖的本質都一樣,都是將并發運行變成串行,以此來控制同一時間內共享資料只能被一個任務所修改,進而保證資料安全,有了GIL的存在,同一時刻同一行程中只有一個執行緒被執行,
可以肯定的一點是:保護不同的資料的安全,就應該加不同的鎖,
參考閱讀:
https://www.cnblogs.com/linhaifeng/articles/7449853.html
八、同步鎖
1、三個需要注意的點:
#1.GIL & Lock
執行緒搶的是GIL鎖,GIL鎖相當于執行權限,拿到執行權限后才能拿到互斥鎖Lock,
其他執行緒也可以搶到GIL,但如果發現Lock仍然沒有被釋放則阻塞,即便是拿到執行權限GIL也要立刻交出來,
(GIL鎖 和 互斥鎖Lock 都搶到 ,才是王道)
#2. join() & Lock
join是等待所有,即整體串行,而鎖只是鎖住修改共享資料的部分,即部分串行,
要想保證資料安全的根本原理在于讓并發變成串行,join與互斥鎖都可以實作,毫無疑問,互斥鎖的部分串行效率要更高
#3. GIL與互斥鎖的經典分析,見本小節末---必看
2、Python已經有一個GIL來保證同一時間只能有一個執行緒來執行了,為什么這里還需要lock?
一個共識 —— 鎖的目的是為了保護共享的資料,同一時間只能有一個執行緒來修改共享的資料
一個結論 —— 保護不同的資料就應該加不同的鎖,
明朗答案 —— GIL 與Lock是兩把鎖,保護的資料不一樣,
前者 GIL 是解釋器級別的(當然保護的就是解釋器級別的資料,比如垃圾回收的資料),
后者Lock 是保護用戶自己開發的應用程式的數據,很明顯GIL不負責這件事,只能用戶自定義加鎖處理,即Lock
3、GIL鎖 VS 互斥鎖Lock ——代碼栗子
鎖通常被用來實作對共享資源的同步訪問,
為每一個共享資源創建一個Lock物件,當你需要訪問該資源時,呼叫acquire方法來獲取鎖物件
(如果其它執行緒已經獲得了該鎖,則當前執行緒需等待其被釋放),待資源訪問完后,再呼叫release方法釋放鎖:
import threading R=threading.Lock() R.acquire() ''' 對公共資料的操作 ''' R.release()
from threading import Thread import os,time def work(): global n temp=n time.sleep(0.1) n=temp-1 if __name__ == '__main__': n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #結果可能為99join/未加鎖
from threading import Thread,Lock import os,time def work(): global n lock.acquire() temp=n time.sleep(0.1) n=temp-1 lock.release() if __name__ == '__main__': lock=Lock() n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #結果肯定為0,由原來的并發執行變成串行,犧牲了執行效率保證了資料安全加鎖后
GIL鎖與互斥鎖綜合分析:
#1.100個執行緒去搶GIL鎖,即搶執行權限#2. 肯定有一個執行緒先搶到GIL(暫且稱為執行緒1),然后開始執行,一旦執行就會拿到lock.acquire()
#3. 極有可能執行緒1還未運行完畢,就有另外一個執行緒2搶到GIL,然后開始運行,但執行緒2發現互斥鎖lock還未被執行緒1釋放,于是阻塞,被迫交出執行權限,即釋放GIL
#4.直到執行緒1重新搶到GIL,開始從上次暫停的位置繼續執行,直到正常釋放互斥鎖lock,然后其他的執行緒再重復2 3 4的程序
4、讓并發變成串行——互斥鎖 和 join()的區別
join是等待所有,即整體串行,而鎖只是鎖住修改共享資料的部分,即部分串行,
要想保證資料安全的根本原理在于讓并發變成串行,join與互斥鎖都可以實作,毫無疑問,互斥鎖的部分串行效率要更高
#不加鎖:并發執行,速度快,資料不安全 from threading import current_thread,Thread,Lock import os,time def task(): global n print('%s is running' %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:0.5216062068939209 n:99 ''' #不加鎖:未加鎖部分并發執行,加鎖部分串行執行,速度慢,資料安全 from threading import current_thread,Thread,Lock import os,time def task(): #未加鎖的代碼并發運行 time.sleep(3) print('%s start to run' %current_thread().getName()) global n #加鎖的代碼串行運行 lock.acquire() temp=n time.sleep(0.5) n=temp-1 lock.release() if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:53.294203758239746 n:0 ''' #有的同學可能有疑問:既然加鎖會讓運行變成串行,那么我在start之后立即使用join,就不用加鎖了啊,也是串行的效果啊 #沒錯:在start之后立刻使用jion,肯定會將100個任務的執行變成串行,毫無疑問,最終n的結果也肯定是0,是安全的,但問題是 #start后立即join:任務內的所有代碼都是串行執行的,而加鎖,只是加鎖的部分即修改共享資料的部分是串行的 #單從保證資料安全方面,二者都可以實作,但很明顯是加鎖的效率更高. from threading import current_thread,Thread,Lock import os,time def task(): time.sleep(3) print('%s start to run' %current_thread().getName()) global n temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() start_time=time.time() for i in range(100): t=Thread(target=task) t.start() t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 start to run Thread-2 start to run ...... Thread-100 start to run 主:350.6937336921692 n:0 #耗時是多么的恐怖 '''互斥鎖與join的區別
九、死鎖現象與遞回鎖——行程和執行緒都有死鎖現象與遞回鎖
所謂死鎖:
是指兩個或兩個以上的行程或執行緒在執行程序中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去,此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的行程稱為死鎖行程,如下就是死鎖:
from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('\033[41m%s 拿到A鎖\033[0m' %self.name) mutexB.acquire() print('\033[42m%s 拿到B鎖\033[0m' %self.name) mutexB.release() mutexA.release() def func2(self): mutexB.acquire() print('\033[43m%s 拿到B鎖\033[0m' %self.name) time.sleep(2) mutexA.acquire() print('\033[44m%s 拿到A鎖\033[0m' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t=MyThread() t.start() ''' Thread-1 拿到A鎖 Thread-1 拿到B鎖 Thread-1 拿到B鎖 Thread-2 拿到A鎖 然后就卡住,死鎖了 '''死鎖現象
解決方法——遞回鎖
在Python中為了支持在同一執行緒中多次請求同一資源,python提供了可重入鎖RLock,
這個RLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require,直到一個執行緒所有的acquire都被release,其他的執行緒才能獲得資源,上面的例子如果使用RLock代替Lock,則不會發生死鎖:
mutexA=mutexB=threading.RLock() #一個執行緒拿到鎖,counter加1,該執行緒內又碰到加鎖的情況,則counter繼續加1,這期間所有其他執行緒都只能等待,等待該執行緒釋放所有鎖,即counter遞減到0為止
十、執行緒 的類 queue
1、執行緒queue
queue佇列 :使用 import queue,用法與行程Queue一樣
(注意區分——行程的Queue,行程的佇列首字母為大寫Q,執行緒的為小寫q
行程中的匯入方法:from multiprocessing import Process,Queue)
2、主要用法
class queue.Queue(maxsize=0) #先進先出
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 結果(先進先出): first second third '''View Code
class queue.LifoQueue(maxsize=0) #last in fisrt out
import queue q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 結果(后進先出): third second first '''View Code
class queue.PriorityQueue(maxsize=0) #存盤資料時可設定優先級的佇列
import queue q=queue.PriorityQueue() #put進入一個元組,元組的第一個元素是優先級(通常是數字,也可以是非數字之間的比較),數字越小優先級越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 結果(數字越小優先級越高,優先級高的優先出隊): (10, 'b') (20, 'a') (30, 'c') '''View Code
參考資料:
https://www.cnblogs.com/linhaifeng/articles/7428877.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/153796.html
標籤:Python
上一篇:第二章 python入門介紹
