
- 初窺Thread類
- 未引進執行緒
- 方式一:創建Thread實體,傳給它一個函式
- 方式二:繼承Thread,并重新定義run方法
- 全域解釋器鎖GIL
執行緒
一個行程中的各個執行緒與主執行緒共享同一片資料空間,因此相對于行程,執行緒間的資訊共享與通訊更加便捷,執行緒以并發方式執行,得益于這種并行與資料共享的機制,使得多任務協作的實作更加簡單,
Python代碼的執行是由Python虛擬機控制,在 CPython 中,由于存在 全域解釋器鎖(GIL),同一時刻只有一個執行緒可以執行,這種限制使得python的多執行緒就像在單CPU上跑多行程,只能做到并發,無法做到并行,
某些 Python I/O 例程 (呼叫了內置的作業系統 C 代碼的那種),GIL 會在 I/O 呼叫前被釋放,以允許其他執行緒在 I/O 執行的時候運行,而對于那些沒有太多 I/O 操作的代碼而言,更傾向于在該執行緒整個時間片內始終占有處理器(和 GIL),總而言之,I/O 密集型的 Python 程式要比計算密集型的代碼能夠更好地利用多執行緒環境,
守護執行緒
守護執行緒一般是一個在后臺為了等待某個事件發生并相應它的執行緒,例如某個守護執行緒運行在服務器端,等待客戶端服務請求,如果沒有客戶端請求,守護行程就一直空閑,通常我們在python的多執行緒中將一個執行緒設定為守護執行緒,就代表這個執行緒不是很重要,python程式將在所有的非守護執行緒結束后才退出,換而言之,python會在所有的非守護執行緒結束后才退出,當所有的非守護執行緒結束后,程式會突然關閉,這時候守護執行緒也會戛然而止,他們的資源(例如打開的檔案,資料庫事物)可能還沒有正確釋放,
初窺Thread類
知其然,更要知其所以然,為了能更好使用多執行緒,我們先來看一下Thread類的建構式,
Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
#group 為日后拓展ThreadGroup類實作而保留
#name 執行緒的名字,是的 也可以為執行緒起名字
#target 是用于run()方法呼叫的物件,默認None,表示不呼叫任何方法
#args 是傳入target目標函式的引數 默認是()
#kwargs 是傳入target目標函式的關鍵字引數 默認是{}
#daemon(譯:守護程式) 如果為Ture則將執行緒設定為守護執行緒,否則新執行緒將繼承當前執行緒的守護模式屬性,因為主執行緒(python程式里初始的控制執行緒)是非守護執行緒,所以它創建的執行緒都默認繼承了他的屬性都是非守護執行緒
Thread執行緒物件被創建后并不意味著一個獨立的執行緒已經被創建,直到呼叫Thread實體的start()方法被呼叫時,一個獨立的執行緒才會被創建,這個執行緒被創建后會做些什么呢?它的作業流程是固定的,即呼叫Thread實體的run()方法,run()咋寫的這個獨立執行緒就咋辦,那這個run()方法是咋寫的嘞? 默認方式是通過建構式傳入的target,kwargs引數來呼叫target, args,kwatgs,target都是我們在建構式中傳進來的,通過這三個引數來并發運行我們想要并發的東東,
- start()方法 創建一個新的獨立執行緒
- 在這個獨立執行緒中運行實體的run()方法
- run()方法的默認行為:將建構式中的 args,kwargs引數傳入target
- 當run()方法運行結束,這個獨立的執行緒就結束了
這就是創建一個執行緒,到一個執行緒銷毀的全程序,有了這些理論知識,現在開始動手實踐驗證,
未引進執行緒
from time import sleep, time
def func(name, t):
print(name, "開始", int(time()))
sleep(t)
print(name, "結束", int(time()))
print("程式開始執行")
start = time()
func("豬", 4)
func("牛", 4)
end = time()
print("總共運行:",int( end - start))
結果
程式開始執行
豬 開始 1618821733
豬 結束 1618821737
牛 開始 1618821737
牛 結束 1618821741
總共運行: 8
方式一:創建Thread實體,傳給它一個函式
既然我們知道了run()的默認行為是將args和kwatgs傳入target 并運行target,那咱們就順著他來,
from threading import Thread
from time import sleep, time
def func(name, t):
print(name, "開始", int(time()))
sleep(t) #睡眠t秒
print(name, "結束", int(time()))
T1 = Thread(target=func,args=('豬',4)) #建構式每個引數都有默認引數,因此可以不使用kwargs
T2 = Thread(target=func,args=('牛',4)) #注意args期待一個元祖,如果只傳一個引數arg1 這樣:(arg1,)
print("主執行緒開始執行")
start = time()
T1.start() #執行緒開始執行
T2.start() #執行緒開始執行
end = time()
print("主執行緒共運行:",int( end - start))
結果
主執行緒開始執行
豬 開始 1618822143
牛 開始 1618822143
主執行緒共運行: 0
豬 結束 1618822147
牛 結束 1618822147
解釋
當T1.start() T2.start() 呼叫時,會分別創建兩個獨立的執行緒,加上主執行緒一共有三個獨立執行緒在python虛擬機中運行,三個獨立執行緒以不可預測的進度運行,因此有了上面的運行結果,
方式二:繼承Thread,并重新定義run方法
start()方法呼叫后會在一個新的獨立執行緒中運行實體的run()方法,我們可以在繼承中重寫run()方法,干掉它的默認行為,
如果子型別多載了建構式,它一定要確保在做任何事前,先發起呼叫基類構造器(Thread.init()),!!!!!
from threading import Thread
from time import sleep, time
def func(name, t):
print(name, "開始", int(time()))
sleep(t)
print(name, "結束", int(time()))
# 簡單版本
class MyThreadEasy(Thread):
def run(self):
#想要并行的干啥直接寫死就好
#T_easy = MyThreadEasy()
#T.start()
# 通用版本
class MyThread(Thread):
def __init__(self,target,args=(), kwargs={}):
Thread.__init__(self)
#如果子型別多載了建構式,它一定要確保在做任何事前,先發起呼叫基類構造器(Thread.__init__()),
self.target = target
self.args = args
self.kwargs = kwargs
def run(self):
self.target(*(self.args),**(self.kwargs))
print("程式開始執行")
start = time()
T1 = MyThread(func,('豬',4))
T2 = MyThread(func,('牛',4))
T1.start()
T2.start()
end = time()
print("總共運行:",int( end - start))
結果
程式開始執行
豬 開始 1618824594
牛 開始 1618824594
總共運行: 0
豬 結束 1618824598
牛 結束 1618824598
全域解釋器鎖GIL
CPython 解釋器所采用的一種機制,它確保同一時刻只有一個執行緒在執行 Python bytecode,此機制通過設定物件模型(包括 dict 等重要內置型別)針對并發訪問的隱式安全簡化了 CPython 實作,給整個解釋器加鎖使得解釋器多執行緒運行更方便,其代價則是犧牲了在多處理器上的并行性,
不過,某些標準庫或第三方庫的擴展模塊被設計為在執行計算密集型任務如壓碩訓哈希時釋放 GIL,此外,在執行 I/O 操作時也總是會釋放 GIL,
創建一個(以更精細粒度來鎖定共享資料的)“自由執行緒”解釋器的努力從未獲得成功,因為這會犧牲在普通單處理器情況下的性能,據信克服這種性能問題的措施將導致實作變得更復雜,從而更難以維護
如果想利用多核心計算機的計算資源,推薦使用 multiprocessing 或 concurrent.futures.ProcessPoolExecutor,
Python虛擬機按照下面所述方式來切換執行緒
切換執行緒被放在一個互斥鎖中
-
設定GIL
-
執行某個執行緒A
-
執行下面操作之一
- 執行一定數量的A的python代碼(位元組碼指令)
- 執行緒主動讓出控制權
-
將A的執行狀態保存,以備下次執行
-
解鎖GIL
,,,,待更新
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/277971.html
標籤:Python
