標籤:學曆 之間 改變 請求 支援 處理 use 情境 出錯
threading模組提供了進階別的線程介面,基於低層級的_thread模組實現。
模組基本方法
該模組定了的方法例如以下:
threading.active_count()
返回當前活躍的Thread對象數量。
傳回值和通過enumerate()返回的列表長度是相等的。
threading.current_thread()
返回當前線程對象,相應調用者的控制線程。
假設調用者的控制線程不是通過threading模組建立,一個功能受限的虛擬線程被返回。
threading.get_ident()
返回當前線程的“線程標識符”。
這是一個非0整數,沒有特定含義,通經常使用於索引線程特定資料的字典。
線程標識符能夠被迴圈使用。
threading.enumerate()
返回當前活躍的全部線程對象的列表。該列表包含精靈線程、被current_thread()建立的虛擬線程對象、和主線程。
它不包含終止的線程和還沒有啟動的線程。
threading.main_thread()
返回主線程對象。
在正常情況下,主線程就是Python解譯器啟動的線程。
threading.settrace(func)
為全部從threading模組啟動的線程設定一個trace函數。
在每一個線程的run()方法被調用前。函數將為每一個線程被傳遞到sys.settrace()。
threading.setprofile(func)
為全部從threading模組啟動的線程設定一個profile函數。在每一個線程的run()方法被調用前,函數將為每一個線程被傳遞到sys.setprofile() 。
threading.stack_size([size])
返回當建立一個新線程是使用的線程棧大小,0表示使用平台或配置的預設值。
平台頂一個常量例如以下:
threading.TIMEOUT_MAX
堵塞函數(Lock.acquire()、RLock.acquire()、Condition.wait()等)的逾時參數同意的最大值。指定的值超過該值將拋出OverflowError。
該模組也定義了一些類,在以下會講到。
該模組的設計是仿照Java的執行緒模式。然而。Java使lock和condition變數成為每一個對象的基本行為。在Python中則是分離的對象。Python的Thread類支援Java的線程類的行為的一個子集;當前,沒有優先順序,沒有線程組,而且線程不能被銷毀、停止、暫停、恢複、或者中斷。當實現時,Java的線程類的靜態方法被相應到模組層級函數。
形容在以下的全部方法都被原子地運行。
執行緒區域資料
執行緒區域資料是那些值和特定線程相關的資料。為了管理執行緒區域資料,建立一個local類(或者一個子類)的執行個體,然後儲存屬性在它裡面:
mydata = threading.local()mydata.x = 1
為不同線程執行個體的值將是不同的。
class threading.local
表示執行緒區域資料的類。
很多其它的細節參考_threading_local的文檔字串。
線程對象
Thread類表示一個執行在一個獨立的控制線程中的行為。有兩個方法指定這個行為:通過傳遞一個callable對象給建構函式,或者通過在子類中重載run()方法,在子類中沒有其它方法(除了建構函式)應該被重載,換句話說,僅重載這個類的__init__()和run()方法。
一旦一個線程對象被建立,他的行為必須通過線程的start()方法啟動。這將在一個獨立的控制線程中調用run()方法。
一旦線程的行為被啟動,這個線程被覺得是‘活躍的‘。
正常情況下,當它的run()終止時它推出活躍狀態。或者出現為處理的異常。is_alive()方法可用於測試線程是否活躍。
其他線程能調用一個線程的join()方法。
這將堵塞調用線程直到join()方法被調用的線程終止。
一個線程有一個名稱,名稱能被傳遞給構造器,並能夠通過name屬性讀取或者改變。
一個線程能被標註為“精靈線程”。
這個標誌的意義是當今有精靈線程遺留時,Python程式將退出。初始值從建立的線程繼承,這個標誌能夠通過daemon屬性設定,或者通過構造器的daemon參數傳入。
注意,精靈線程在關閉時會突然地停止。他們的資源(比如開啟的檔案、資料庫事務等)不能被正確的釋放。假設你想你的線程優雅地停止,應該使它們是非精靈線程而且使用一個適當的訊號機制,比如Event(後面解說)。
有一個“主線程”對象,這相應到Python程式的初始控制線程,它不是精靈線程。
有可能“虛擬線程對象”被建立,則會存線上程對象相應到“外星人線程”,其控制線程在threading模組之外啟動,比如直接從C代碼啟動。虛擬線程對象的功能是受限的,它們總是被覺得是活躍的精靈線程,不能被join()。
他們不能被刪除,因為探測外星人線程的終止是不可能的。
以下是Thread類的構造方法:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
1)group:應該是None,保留為未來的擴充,當一個ThreadGroup類被實現的時候須要;
2)target:一個callable對象,被run()方法調用。默覺得None,意味著什麼都不做。
3)name:線程名,預設情況下,一個形式為“Thread-N”的唯一名被構造,N是一個小的十進位數;
4)args:調用target的參數元組,默覺得();
5)kwargs:為target調用的參數字典,默覺得{};
6)daemon:假設不為None。則設定該線程是否為精靈線程。假設為None,則從當前線程繼承;
假設子類覆蓋了構造器。在做其他不論什麼事情之前。它必須確保先調用基類的構造器(Thread.__init__())。
線程的經常用法例如以下:
1)start()
啟動線程。
每一個線程最多僅僅能調用一次。它會導致對象的run()方法在獨立的控制線程中被調用。
假設在同一個線程對象上調用超過一次將拋出RuntimeError。
2)run()
表示線程行為的方法。
你能夠在子類中重載該方法。
標準的run()方法調用傳入到構造器中callable對象(相應target參數),使用相應的args或者kwargs參數。
3)join(timeout=None)
等待直到線程結束。這將堵塞當前線程直到join()方法被調用的線程終止或者拋出一個未處理的異常,或者設定的溢出時間到達。
假設timeout參數指定且為非None,則它應該是一個浮點數,用於指定操作的溢出時間,單位為秒。因為join()總是返回None,因此在join()結束後你必須調用is_alive()來推斷線程是否結束。假設線程任然是活躍的,join()調用則是時間溢出。
當timeout不被指定,或者指定為None時。操作將堵塞直到線程終止。
一個線程能被join()多次。
假設一個join當前線程的嘗試將導致一個死結,join()將拋出RuntimeError。
在一個線程啟動之前對該線程做join()操作也將導致相同的異常。
4)name
線程名,僅用於標識一個線程,沒有語義,多個線程能夠被給相同的名字,初始的名稱被構造器設定。
5)getName()
setName()
name的舊的getter/setter API。如今直接使用name屬性取代。
6)ident
這個線程的“線程標識符”,假設線程沒有啟動,則為None。
這是一個非0整數。
線程標識符能夠被迴圈使用,一個線程退出後它的線程標識符能夠被其他線程使用。
7)is_alive()
返回線程是否活躍。
該方法僅僅有在run()啟動後而且在終止之前才返回True。模組函數enumerate()返回全部活躍線程的一個列表。
8)daemon
一個布爾值。用於表示該線程是否精靈線程。
該值必須在start()方法調用之前設定,否則RuntimeError被拋出。它的初始值從建立的線程繼承。主線程不是一個精靈線程,因此全部在主線程中建立的線程daemon默覺得False。
當沒有活躍的非精靈線程執行時。整個Python程式退出。
9)isDaemon()
setDaemon()
老的getter/setter API。如今直接使用daemon屬性取代。
Lock對象
基元鎖是不被特定線程擁有的同步基元。
在Python中。它是當前可用的最低層級的同步基元,通過_thread擴充模組直接實現。
一個基元鎖存在兩種狀態,“鎖”或者“未鎖”,初始建立時處於未鎖狀態。
他有兩個基本方法:acquire()和release()。當狀態是未鎖時,acquire()將改變其狀態到鎖而且馬上返回;當狀態是鎖時,acquire()堵塞直到還有一個線程調用release()釋放了鎖,然後acquire()擷取鎖並重設鎖的狀態到鎖而且返回。
release()應該僅僅在鎖處理鎖狀態時才調用,它改變鎖的狀態到未鎖而且馬上返回。假設嘗試釋放一個未鎖的鎖,一個RuntimeError將被拋出。
鎖也支援上下文管理協議。
當超過一個線程被鎖堵塞,當鎖被釋放後僅有一個線程能擷取到鎖,擷取到鎖的線程不確定。依賴詳細的實現。
相關類例如以下:
class threading.Lock
該類實現了基元鎖對象。線程能夠通過acquire請求該鎖。假設已經存在其他線程擷取了鎖。則線程堵塞。直到其他線程釋放鎖。
1)acquire(blocking=True, timeout=-1)
請求一個鎖,堵塞或者非堵塞。
當blocking參數為True(預設),將堵塞直到鎖被釋放。然後擷取鎖並返回True。
當blocking參數為False,將不堵塞。假設已經存線上程擷取了鎖,調用將馬上返回False。否則。將擷取鎖並返回True。
當timeout參數大於0時。最多堵塞timeout指定的秒值。
timeout為-1(預設)表示一直等待。當blocking為False時不同意指定timeout參數。
假設鎖請求成功。則返回True,否則返回False(比如逾時)。
2)release()
釋放一個鎖。這能在不論什麼線程中調用,不僅在擷取鎖的線程中。
當鎖處於鎖狀態時,重設它為未鎖,並返回。其他堵塞等待該鎖的線程中將有一個線程能擷取到鎖。
在一個未鎖的鎖上調用該方法。將拋出RuntimeError。
無傳回值。
RLock對象
一個可重新進入鎖能夠被同一個線程請求多次。在內部,它在基元鎖的基礎上使用了“擁有者線程”和“遞迴層級”的概念。在鎖狀態,一些線程擁有鎖;在未鎖狀態。沒有線程擁有鎖。
為了擷取鎖,一個線程調用acquire()方法。擷取鎖後返回;為了釋放鎖,一個線程調用release()方法。acquire()/release()的調用能夠是嵌套的,僅僅有最後的release()重設鎖到未鎖。
可重新進入鎖也支援上下文管理協議。
class threading.RLock
該類實現了可重新進入鎖對象。一個可重新進入鎖必須被請求它的線程釋放,一旦一個線程擁有了一個可重新進入鎖,該線程能夠再次請求它,注意請求鎖的次數必須和釋放鎖的次數相應。
注意RLock實際上是一個工廠函數,返回當前平台支援的效率最高的RLock類版本號碼的一個執行個體。
1)acquire(blocking=True, timeout=-1)
請求一個鎖,堵塞或者非堵塞方式。
當參數為空白時:假設這個線程已經擁有鎖,遞迴層級加一,然後返回。否則,假設還有一個線程擁有鎖,堵塞直到鎖被釋放。
假設鎖處於未鎖狀態(不被不論什麼線程擁有),則設定擁有者線程。並設定遞迴層級為1,然後返回。假設超過一個線程處於堵塞等待隊列中,一次僅有一個線程能擷取鎖。
該情境沒有傳回值。
當blocking為True時,和沒有參數的情境同樣,並返回True。
當blocking為False時。將不堵塞。假設鎖處於鎖狀態,則馬上返回False;否則。和沒有參數的情境同樣,並返回True。
當timeout參數大於0時。最多堵塞timeout秒。假設在timeout秒內擷取了鎖,則返回True,否則逾時返回False。
2)release()
釋放一個鎖,降低遞迴層級。假設遞迴層級降低到0,則重設鎖的狀態到未鎖(不被不論什麼線程擁有)。假設降低後遞迴層級任然大於0。則鎖任然被調用者線程保持。
僅當調用者線程擁有鎖時才調用該方法。否則拋出RuntimeError。
沒有傳回值。
Condition對象
condition變數總是和鎖相關聯,這能被傳入或者通過預設建立。
當幾個condition變數必須共用同一個鎖時傳入是實用的。鎖是condition對象的一部分:你不必分別跟蹤它。
condition變數遵守上下文管理協議:在代碼塊中用with語句擷取相關的鎖。acquire()和release()方法也調用相關鎖的相應方法。
其他方法必須被相關鎖的持有人調用。
wait()方法釋放鎖,然後堵塞直到還有一個線程調用notify()或者notify_all()喚醒它。喚醒後,wait()又一次擷取鎖並返回。它也能夠指定一個逾時時間。
notify()方法喚醒等待線程中的一個;notify_all()方法喚醒全部等待線程。
注意:notify()和notify_all()方法不釋放鎖;這意味著喚醒的線程或者線程組將不會從wait()調用中馬上返回。
使用condition變數的一個典型的應用就是用鎖同步對一些共用狀態的進入;對某個特定狀態感興趣的線程重複調用wait()。直到出現他們感興趣的狀態。改動這個狀態的線程則調用notify()或者notify_all()來通知等待的線程狀態已經改變。比如。以下是一個典型的使用了無限緩衝的生產者-消費者模式:
# 消費一個條目with cv: while not an_item_is_available(): cv.wait() get_an_available_item()# 產生一個條目with cv: make_an_item_available() cv.notify()
while迴圈檢查是否有條目可用。由於wait()能夠在等待隨意時間後返回,也有可能調用notify()的線程並沒有使條件為真。在多線程編程中這個問題始終存在。wait_for()方法能被用於自己主動的條件檢測,簡化逾時的計算:
# 消費一個條目with cv: cv.wait_for(an_item_is_available) get_an_available_item()
對於notify()和notify_all()。使用哪個在於應用情境中有一個還是多個等待線程。
比如。在一個典型的生產者-消費者情境中。添加一個條目到緩衝僅須要喚醒一個消費者線程。
class threading.Condition(lock=None)
該類實現條件變數對象。一個條件變數同意一個或多個線程等待。直到他們被還有一個線程通知。
假設lock參數被指定且不為None。它必須是Lock或者RLock對象,被用做隱含鎖。
否則。一個新的RLock對象被建立並作為隱含鎖。
1)acquire(*args)
請求一個隱含鎖,這種方法會調用隱含鎖的相應方法。傳回值即為隱含鎖的方法的傳回值。
2)release()
釋放隱含鎖。
這種方法調用隱含鎖的相應方法。沒有傳回值。
3)wait(timeout=None)
等待直到被喚醒。或者逾時。
假設調用線程沒有請求鎖。RuntimeError被拋出。
該方法會釋放隱含鎖,然後堵塞直到它被還有一個線程調用同一個condition對象的notify()或者notify_all()方法喚醒,或者直到指定的timeout時間溢出。一旦喚醒或者逾時,它又一次請求鎖並返回。
當timeout參數被指定並不為None,則指定了一個秒級的逾時時間。
當隱含鎖是一個RLock鎖,它不通過release()方法釋放鎖,由於假設線程請求了多次鎖,使用release()方法不能解鎖(必須調用和lock方法同樣的次數才幹解鎖)。
一個RLock類的內部介面被使用,該介面能釋放鎖,無論鎖請求了多少次。
當鎖被請求時,還有一個內部介面被用於還原鎖的遞迴層級。
方法返回True,假設逾時則返回False。
4)wait_for(predicate, timeout=None)
等待直到條件為True。
predicate應該是一個callable,傳回值為布爾值。
timeout用於指定逾時時間。
該方法相當於重複調用wait()直到條件為真,或者逾時。傳回值是最後的predicate的傳回值。或者逾時返回Flase。
忽略逾時特性,調用這種方法相當於:
while not predicate(): cv.wait()
因此。調用該方法和調用wait()具有相同的規則:調用是或者從堵塞中返回時必須先擷取鎖。
5)notify(n=1)
預設情況下,喚醒一個等待線程。假設調用該方法的線程沒有擷取鎖,則RuntimeError被拋出。
這種方法子多喚醒n(默覺得1)個等待線程;假設沒有線程等待。則沒有操作。
假設至少n個線程正在等待。當前的實現是剛好喚醒n個線程。
然而,依賴這個行為是不安全的,由於,未來某些最佳化後的實現可能會喚醒超過n個線程。
注意:一個喚醒的線程僅僅有當請求到鎖後才會從wait()調用中返回。因為notify()不釋放鎖,所以它的調用者應該釋放鎖。
6)notify_all()
喚醒全部等待線程。這種方法的行為類似於notify(),可是喚醒全部等待線程。假設調用線程未擷取鎖,則RuntimeError被拋出。
Semaphore對象
這是電腦科學曆史上最早的同步基元中的一個,被荷蘭的電腦科學家Edsger W. Dijkstra發明(他使用P()和V()而不是acquire()和release())。
semaphore管理一個內部計數,每次調用acquire()時該計數減一,每次調用release()時計數加一。
計數不會小於0。當acquire()發現計數為0時,則堵塞。等待直到其他線程調用release()。
semaphore也支援上下文管理協議。
class threading.Semaphore(value=1)
該類實現semaphore對象。一個semaphore管理一個表示計數表示能並行進入的線程數量。假設計數為0。則acquire()堵塞直到計數大於0。
value默覺得1.
value給出了內部計數的初始值,默覺得1。假設傳入的value小於0,則拋出ValueError。
1)acquire(blocking=True, timeout=None)
請求semaphore。
當沒有參數時:假設內部計數大於0。將計數減1並馬上返回。假設計數為0。堵塞。等待直到還有一個線程調用了release()。這使用互鎖機制實現,保證了假設有多個線程調用acquire()堵塞。則release()將僅僅會喚醒一個線程。喚醒的線程是隨機播放一個,不依賴堵塞的順序。
成功返回True(或者無限堵塞)。
假設blocking為False,將不堵塞。假設無法擷取semaphore。則馬上返回False;否則。同沒有參數時的操作,並返回True。
當設定了timeout而且不是None,它將堵塞最多timeout秒。假設在該時間內沒有成功擷取semaphore,則返回False;否則返回True。
2)release()
釋放一個semaphore。計數加1。假設計數初始為0,則須要喚醒等待隊列中的一個線程。
class threading.BoundedSemaphore(value=1)
該類實現了有界的semaphore對象。一個有界的semaphore會確保它的當前值沒有溢出他的初始值。假設溢出。則ValueError被拋出。
在大部分情境下。semaphore被用於限制資源的使用。
假設semaphore被釋放太多次,往往表示出現了bug。
Semaphore使用執行個體
Semaphore通常被用於控制資源的使用。比如。一個資料庫server。在一些情況下。資源的大小被固定,你應該使用一個有界的Semaphore。
在啟動其它背景工作執行緒之前。你的主線程首先初始化Semaphore:
maxconnections = 5# ...pool_sema = BoundedSemaphore(value=maxconnections)
其他背景工作執行緒則在須要串連到server時調用Semaphore的請求和釋放方法:
with pool_sema: conn = connectdb() try: # ... use connection ... finally: conn.close()
有個有界的Semaphore能夠降低程式出錯的機會,防止semaphore釋放的次數大於請求次數引發的問題。
Event對象
這是線上程間最簡單的通訊機制之中的一個:一個線程通知一個事件,還有一個線程等待通知並作出處理。
一個Event對象管理一個內部標誌,使用set()方法能夠將其設定為True,使用clear()方法能夠將其重設為False。wait()方法將堵塞直到標誌為True。
class threading.Event
該類實現事件對象。
一個事件管理一個標誌,能夠通過set()方法將其設定為True,通過clear()方法將其重設為False。wait()方法堵塞直到標誌為True。
標誌初始為False。
1)is_set()
若且唯若內部標誌為True時返回True。
2)set()
設定標誌為True。
全部等待的線程都將被喚醒。一旦標誌為True。調用wait()的線程將不再堵塞。
3)clear()
重設標誌為False。
接下來,全部調用wait()的線程將堵塞直到set()被調用。
4)wait(timeout=None)
堵塞直到標誌被設定為True。假設標誌已經為True,則馬上返回;否則,堵塞直到還有一個線程調用set(),或者直到逾時。
當timeout參數被指定且不為None,線程將僅等待timeout的秒數。
該方法當標誌為True時返回True,當逾時時返回False。
Timer對象
該類表示了一個定時器,表示一個行為在多少時間後被運行。Timer是Thread的子類,能夠作為建立自己定義線程的一個執行個體。
Timer通過調用start()方法啟動,通過調用cancel()方法停止(必須在行為被運行之前)。Timer運行指定行為之間的等待時間並非精確的,也就是說可能與使用者指定的間隔存在差異。
比如:
def hello(): print("hello, world")t = Timer(30.0, hello)t.start() # 30秒後,"hello, world"將被列印
class threading.Timer(interval, function, args=None, kwargs=None)
建立一個Timer,在interval秒之後,將使用參數args和kwargs作為參數運行function。假設args為None(預設),將使用空list。假設kwargs是None(預設)。則使用空字典。
1)cancel()
停止定時器。而且取消定時器的行為的運行。這僅當定時器任然處理等待狀態時才有效。
Barrier對象
柵欄提供了一個簡單的同步基元,用於固定數量的線程須要等待彼此的情境。
嘗試通過柵欄的每一個線程都會調用wait()方法,然後堵塞直到全部的線程都調用了該方法。然後,全部線程同一時候被釋放。
柵欄能被反覆使用隨意多次,但必須是同等數量的線程。
以下是一個範例,一個同步client和服務端線程的簡單方法:
b = Barrier(2, timeout=5)def server(): start_server() b.wait() while True: connection = accept_connection() process_server_connection(connection)def client(): b.wait() while True: connection = make_connection() process_client_connection(connection)
class threading.Barrier(parties, action=None, timeout=None)
為parties個線程建立一個柵欄對象,假設提供了action,則當線程被釋放時。它將被線程中的一個調用。timeout表示wait()方法的預設逾時時間值。
1)wait(timeout=None)
通過柵欄。當全部使用柵欄的線程都調用了該方法後,他們將被同一時候釋放。假設timeout被提供,他優先於類構造器提供的timeout參數。
傳回值是0到parties的整數,每一個線程都不同。這能用於選擇某個特定的線程做一些特定的操作,比如:
i = barrier.wait()if i == 0: # Only one thread needs to print this print("passed the barrier")
假設一個action被提供給了構造器,線程中的當中一個將在被釋放時調用它。假設調用拋出一個異常。則柵欄進入損壞狀態。
假設調用逾時。則柵欄進入損壞狀態。
假設柵欄處於損壞狀態,或者有線程在等待時被重設了。則該方法會拋出BrokenBarrierError異常。
2)reset()
恢複柵欄到預設狀態。
不論什麼處於等待中的線程將收到BrokenBarrierError異常。
注意這裡須要額外的同步。假設一個柵欄被損壞,建立一個新的柵欄或許是更好的選擇。
3)abort()
放置柵欄到損壞狀態。
這導致當前處於等待的線程和未來對wait()的調用都會拋出BrokenBarrierError。通常使用該方法是為了避免死結。
使用一個逾時時間應該是更好的選擇。
4)parties
要求通過柵欄的線程的數量。
5)n_waiting
當前處於等待中的線程數量。
6)broken
柵欄是否處於損壞狀態,假設是則為True。
exception threading.BrokenBarrierError
該異常是RuntimeError的子類,當柵欄對象被重設或者損壞時被拋出。
在with語句中使用locks、conditions和semaphores
全部被該模組提供的具有acquire()和release()方法的對象都能使用with語句管理。
當進入堵塞狀態時acquire()方法將被調用,而當推出堵塞狀態時release()方法將被調用。以下是詳細的文法:
with some_lock: # do something...
等價於:
some_lock.acquire()try: # do something...finally: some_lock.release()
當前,Lock、RLock、Condition、Semaphore和BoundedSemaphore都能夠在with語句中管理。
Python多線程1:threading