python並發編程之多線程
開啟線程的兩種方式:
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(‘主線程‘)方式一
from threading import Thread import time class Sayhi(Thread): def__init__(self,name): supper().方式二__init__() self.name=name def run(self): time.sleep(2): print(‘%s say hello‘%self.name) if __name__==‘__main__‘: t=Say(‘egon‘) t.start() print(‘主線程‘)
在這裏我要說明一下他們誰的開啟速度快
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
很明顯我們可以看到:在線程裏面會先打印子線程在打印主線程,而在進程裏面會先打印主進程然後打印子進程。(在這裏我想簡單的說一下,就是說你開啟一個進程,你得去重新獲得資源,然而開啟線程的時候,資源已經存在了,不需要去開辟新的資源,所以它的開啟速度就會明顯快了好多)
補充:開啟一個線程,在他之上有一個進程,我們在開啟線程的時候回自動產生一個線程,這個線程就叫做主線程,開啟的線程叫做其他線程(為什麽不叫做子線程呢,就是因為在這裏線程他只是共享資源,他們之間沒有任何的依賴關系)
接下來講一下:同一進程內的線程共享該進程的數據(資源)
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,因為同一進程內的線程之間共享進程內的數據
(在這裏我要簡單的說明一下,為什麽同一進程內的線程可以共享該進程的數據,從這個實例中我們可以清楚的看到,在進程中,子進程他只是將自己的n改成了0,而父進程的n始終都是100,而對於線程來說,n的結果是0,這就是因為,同一進程內的線程共享該進程的數據)
實例:三個任務,一個接收用戶輸入,一個將用戶輸入的內容格式化成大寫,一個將格式化後的結果存入文件(首先,在這裏面三個任務是同時執行的,)
from threading import Thread msg_l=[] format_l=[] def talk(): while True: msg=input(‘>>: ‘).strip() if not msg:continue msg_l.append(msg) def format_msg(): while True: if msg_l: res=msg_l.pop() format_l.append(res.upper()) def save(): while True: if format_l: with open(‘db.txt‘,‘a‘,encoding=‘utf-8‘) as f: res=format_l.pop() f.write(‘%s\n‘ %res) if __name__ == ‘__main__‘: t1=Thread(target=talk) t2=Thread(target=format_msg) t3=Thread(target=save) t1.start() t2.start() t3.start()
線程相關的其他方法:
Thread實例對象的方法 # isAlive(): 返回線程是否活動的。 # getName(): 返回線程名。 # setName(): 設置線程名。 threading模塊提供的一些方法: # threading.currentThread(): 返回當前的線程變量。 # threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啟動後、結束前,不包括啟動前和終止後的線程。 # 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 ‘‘‘
主線程等待子線程結束
復制代碼 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 ‘‘‘ 復制代碼
守護線程:
無論是進程還是線程,都是:守護xxx會等待主xxx完畢後被銷毀,
主進程與主線程在什麽情況下才算運行完畢
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.分析Lock的同時一定要說明:線程搶的是GIL鎖,拿到執行權限後才能拿到互斥鎖Lock #2.使用join與加鎖的區別:join是等待所有,即整體串行,而鎖只是鎖住一部分,即部分串行 #3. 一定要看本小節最後的GIL與互斥鎖的經典分析
GIL VS Lock
機智的同學可能會問到這個問題,就是既然你之前說過了,Python已經有一個GIL來保證同一時間只能有一個線程來執行了,為什麽這裏還需要lock?
首先我們需要達成共識:鎖的目的是為了保護共享的數據,同一時間只能有一個線程來修改共享的數據
然後,我們可以得出結論:保護不同的數據就應該加不同的鎖。
最後,問題就很明朗了,GIL 與Lock是兩把鎖,保護的數據不一樣,前者是解釋器級別的(當然保護的就是解釋器級別的數據,比如垃圾回收的數據),後者是保護用戶自己開發的應用程序的數據,很明顯GIL不負責這件事,只能用戶自定義加鎖處理,即Lock
過程分析:所有線程搶的是GIL鎖,或者說所有線程搶的是執行權限
線程1搶到GIL鎖,拿到執行權限,開始執行,然後加了一把Lock,還沒有執行完畢,即線程1還未釋放Lock,有可能線程2搶到GIL鎖,開始執行,執行過程中發現Lock還沒有被線程1釋放,於是線程2進入阻塞,被奪走執行權限,有可能線程1拿到GIL,然後正常執行到釋放Lock。。。這就導致了串行運行的效果
既然是串行,那我們執行
t1.start()
t1.join
t2.start()
t2.join()
這也是串行執行啊,為何還要加Lock呢,需知join是等待t1所有的代碼執行完,相當於鎖住了t1的所有代碼,而Lock只是鎖住一部分操作共享數據的代碼。
python並發編程之多線程