python之路-day33-執行緒相關1
一、執行緒的理論
1、什麼是執行緒
1) 在傳統作業系統中,每個程序有一個地址空間,而且預設就有一個控制執行緒
2)執行緒顧名思義,就是一條流水線工作的過程(流水線的工做需要電源,電源就相當於cpu),而一條流水線必須屬於一個車間,
一個車間的工作過程就是一個程序,車間負責把資源整合到一起,是一個資源單位,而一個車間至少有一條流水線。
3)所以:程序只是用來把資源集中到一起(程序只是一個資源單位,或者時候資源集合),而執行緒才是cpu上的執行單位
4) 多執行緒(即多個控制執行緒)的概念是,在一個程序中存在多個執行緒,多個執行緒共享該程序的地址空間,相當與一個車間內有
多天流水線,都公用一個車間的資源。例如,北京地鐵與上海地鐵是不同的程序,而北京地鐵裡的13號線是一個執行緒,北京地鐵所有
的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。
2、執行緒與程序的區別
1)同一個程序內的多個執行緒共享該程序內的地址資源
2)建立執行緒的開銷要遠小於建立程序的開銷(建立一個程序,就是建立一個車間,設計到申請空間,而且在該空間
內至少建一條流水線,但建立執行緒,就只是在一個車間內造一條流水線,無需申請空間,所以建立開銷小)
二、開啟執行緒的兩種方式
1、threading模組介紹
multiprocess模組的完全模仿了threading模組的介面,二者在使用層面,有很大的相似性,因為不在詳細的介紹
2、開啟執行緒的兩種方式
1 # 開啟執行緒的兩種方式 2 3 # 方式一 4 # from threading import Thread 5 # import time 6 # 7 # def sayhi(name): 8 # time.sleep(2) 9 # print('%s say hello'%name) 10 # 11 # if __name__ == '__main__':建立執行緒的兩種方式12 # t = Thread(target=sayhi,args=('egon',)) 13 # t.start() 14 # print('主執行緒') 15 16 # 方式二 17 import time 18 from threading import Thread 19 20 class Sayhi(Thread): 21 def __init__(self,name): 22 super().__init__() 23 self.name = name 24 25 def run(self): 26 time.sleep(2) 27 print('%s say hello'%self.name) 28 29 if __name__ == '__main__': 30 t = Sayhi('egon') 31 t.start() 32 print('主執行緒')
三、多執行緒和多程序的區別
1、誰的開啟速度快
from threading import Thread def work(): print('hello') if __name__ == '__main__': t = Thread(target=work) t.start() print('主執行緒/主程序')
執行結果如下,幾乎t.start()的同時就將執行緒開啟了,然後打印出了hello,證明 執行緒的建立開銷極小
hello 主執行緒/主程序
from multiprocessing import Process def work(): print('hello') if __name__ == '__main__': #在主程序下開啟子程序 p = Process(target=work) p.start() print('主程序/主執行緒') # 執行結果如下,p.start()將開啟程序的訊號發給作業系統,作業系統要申請記憶體 # 空間,拷貝父程序地址空間到子程序,開銷遠大於執行緒 # 主程序/主執行緒 # hello
2、瞅一瞅 pid
1 # 在主程序下開啟多個執行緒,每個執行緒都跟主程序的pid一樣 2 # from threading import Thread 3 # import os 4 # 5 # def work(): 6 # print('hello',os.getpid()) 7 # 8 # if __name__ == '__main__': 9 # t1 = Thread(target=work) 10 # t2 = Thread(target=work) 11 # t1.start() 12 # t2.start() 13 # print('主執行緒/主程序pid',os.getpid()) 14 # 15 # 結果: 16 # hello 9856 17 # hello 9856 18 # 主執行緒/主程序pid 9856 19 20 # 開多個程序,每個程序都有不同的pid 21 # from multiprocessing import Process 22 # import os 23 # 24 # def work(): 25 # print('hello',os.getpid()) 26 # 27 # if __name__ == '__main__': 28 # p1 = Process(target=work) 29 # p2 = Process(target=work) 30 # p1.start() 31 # p2.start() 32 # print('主程序/主執行緒',os.getpid()) 33 # 34 # 結果: 35 # 主程序/主執行緒 38384 36 # hello 37008 37 # hello 36496執行緒、程序中的pid
3、同一程序內的執行緒共享該程序的資料?
1 # 程序之間地址空間是隔離的 2 # from multiprocessing import Process 3 # import os 4 # 5 # def work(): 6 # global n 7 # n = 0 8 # 9 # if __name__ == '__main__': 10 # n = 100 11 # p = Process(target=work) 12 # p.start() 13 # p.join() 14 # print('主',n) 15 # 16 # 結果分析:毫無疑問子程序p已將自己的全域性n改成了0.但是改的僅僅是自己的 17 # 父程序的n仍然為100 18 19 20 # 同一程序內開啟的多個執行緒是共享該程序地址空間的 21 # from threading import Thread 22 # import os 23 # 24 # def work(): 25 # global n 26 # n = 0 27 # if __name__ == '__main__': 28 # n = 100 29 # t = Thread(target=work) 30 # t.start() 31 # t.join() 32 # print('主',n) 33 # 結果:檢視結果為0,因為統一程序內的執行緒之間共享程序內的資料 34 # 主 0程序與程序\程序與執行緒之間資料問題
四、Thread 物件的其他屬性或方法
介紹:
# isAlive(): 返回執行緒是否活動的。 # getName(): 返回執行緒名。 # setName(): 設定執行緒名 threading 模組提供的一些方法: # threading.currentThread(): 返回當前執行緒變數 # threading.enumerate(): 返回一個包含正在執行的執行緒的list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒 # threading.activeCount(): 返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果
驗證:
1 # from threading import Thread 2 # import threading 3 # from multiprocessing import Process 4 # import os,time 5 # 6 # def work(): 7 # time.sleep(3) 8 # print(threading.current_thread().getName()) 9 # if __name__ == '__main__': 10 # # 在主程序下開啟執行緒 11 # t = Thread(target=work) 12 # t.start() 13 # print(threading.current_thread().getName()) 14 # print(threading.current_thread()) 15 # print(threading.enumerate()) 16 # print(threading.activeCount) 17 # print('主程序\主執行緒') 18 # 19 # 結果: 20 # MainThread 21 # <_MainThread(MainThread, started 39404)> 22 # [<_MainThread(MainThread, started 39404)>, <Thread(Thread-1, started 39468)>] 23 # <function active_count at 0x000001CBF2F45BF8> 24 # 主程序\主執行緒 25 # Thread-1Thread物件的其他屬性方法
1 # 主執行緒等待子程序結束 2 # from threading import Thread 3 # import time 4 # def sayhi(name): 5 # time.sleep(2) 6 # print('%s say hello'%name) 7 # if __name__ == '__main__': 8 # t = Thread(target=sayhi,args=('egon',)) 9 # t.start() 10 # t.join() 11 # print('主程序') 12 # print(t.is_alive()) 13 # 14 # 執行結果: 15 # egon say hello 16 # 主程序 17 # False主執行緒等待子執行緒結束
五、守護執行緒
無論是程序還是執行緒,都遵循:守護xxx會主動等待主xxx執行完畢後被銷燬
需要強調的是:執行完畢並非終止執行
1、對主程序來說,執行完畢指的是主程序程式碼執行完畢 2、對主執行緒來說,執行完畢指的是主執行緒所在的程序內所有非守護執行緒統統執行完畢,主執行緒才算執行完畢 詳細解釋: 1、主程序在其程式碼結束後就已經運算完畢了(守護程序在此時就被回收),然後主程序 會一直等飛受賄的子程序都執行完畢後回收子程序的資源(否則會產生殭屍程序),才結束 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、所謂死鎖:是指兩個或兩個以上的程序或執行緒執行過程中,因為爭奪資源而造成的一種互相等待的現象,若無外力
作用,它們都將無法推進下去。此時稱系統處於死鎖或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序,如下就是死鎖
1 # from threading import Thread,Lock 2 # import time 3 # 4 # muteA = Lock() 5 # muteB = Lock() 6 # 7 # class MyThread(Thread): 8 # def run(self): 9 # self.fun1() 10 # self.fun2() 11 # 12 # def fun1(self): 13 # muteA.acquire() 14 # print('%s拿到A鎖'%self.name) 15 # 16 # muteB.acquire() 17 # print('%s 拿到B鎖'%self.name) 18 # muteB.release() 19 # muteA.release() 20 # 21 # def fun2(self): 22 # muteB.acquire() 23 # print("%s 拿到B鎖"%self.name) 24 # time.sleep(2) 25 # 26 # muteA.acquire() 27 # print("%s 拿到A鎖"%self.name) 28 # muteA.release() 29 # 30 # muteB.release() 31 # 32 # if __name__ == '__main__': 33 # for i in range(10): 34 # t = MyThread() 35 # t.start() 36 # 37 # 結果: 38 # Thread-1拿到A鎖 39 # Thread-1 拿到B鎖 40 # Thread-1 拿到B鎖 41 # Thread-2拿到A鎖死鎖
2、遞迴鎖
解決辦法:遞迴鎖,在python中為了支援在同一執行緒中多次請求同一資源,python提供了可重入鎖RLock個RLock內部維護著
一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個執行緒所有的acquire都
被release,其他的執行緒才能獲得資源。上面的例子如果使用RLock代替Lock,則不會發生死鎖,二者的區別是:遞迴鎖可以連續
acquire多次,而互斥鎖只能acquire一次
1 from threading import Thread,RLock 2 import time 3 4 mutexA=mutexB=RLock() #一個執行緒拿到鎖,counter加1,該執行緒內又碰到加鎖的情況,則counter繼續加1,這期間所有其他執行緒都只能等待,等待該執行緒釋放所有鎖,即counter遞減到0為止 5 6 class MyThread(Thread): 7 def run(self): 8 self.func1() 9 self.func2() 10 def func1(self): 11 mutexA.acquire() 12 print('\033[41m%s 拿到A鎖\033[0m' %self.name) 13 14 mutexB.acquire() 15 print('\033[42m%s 拿到B鎖\033[0m' %self.name) 16 mutexB.release() 17 18 mutexA.release() 19 20 def func2(self): 21 mutexB.acquire() 22 print('\033[43m%s 拿到B鎖\033[0m' %self.name) 23 time.sleep(2) 24 25 mutexA.acquire() 26 print('\033[44m%s 拿到A鎖\033[0m' %self.name) 27 mutexA.release() 28 29 mutexB.release() 30 31 if __name__ == '__main__': 32 for i in range(10): 33 t=MyThread() 34 t.start()遞迴鎖