python使用多執行緒
threading 模組支援守護執行緒, 其工作方式是:守護執行緒一般是一個等待客戶端請求服務的伺服器。
如果把一個執行緒設定為守護執行緒,程序退出時不需要等待這個執行緒執行完成。
如果主執行緒準備退出時,不需要等待某些子執行緒完成,就可以為這些子執行緒設定守護執行緒標記。 需要在啟動執行緒之前執行如下賦值語句: thread.daemon = True,檢查執行緒的守護狀態也只需要檢查這個值即可。
整個 Python 程式將在所有非守護執行緒退出之後才退出, 換句話說, 就是沒有剩下存活的非守護執行緒時才退出。
使用thread模組
以下是三種使用 Thread 類的方法(一般使用第一個或第三個方案)
-
建立 Thread 的例項,傳給它一個函式。
import threading from time import sleep, ctime loops = [3, 2, 1, 1, 1] def loop(i, nsec): print(f'start loop {i} at: {ctime()}') sleep(nsec) print(f'end loop {i} at: {ctime()}') def main(): print('start at', ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = threading.Thread(target=loop, args=(i, loops[i])) threads.append(t) for i in nloops: # start threads threads[i].start() for i in nloops: # wait for all threads[i].join() # threads to finish print(f'all done at: {ctime()}') if __name__ == '__main__': main()
當所有執行緒都分配完成之後,通過呼叫每個執行緒的 start()方法讓它們開始執行,而不是 在這之前就會執行。 相比於管理一組鎖(分配、獲取、釋放、檢查鎖狀態等)而言,這裡只 需要為每個執行緒呼叫 join()方法即可。 join()方法將等待執行緒結束,或者在提供了超時時間的情況下,達到超時時間。 使用 join()方法要比等待鎖釋放的無限迴圈更加清晰(這也是這種鎖 又稱為自旋鎖的原因)。
-
建立 Thread 的例項,傳給它一個可呼叫的類例項。
import threading from time import sleep, ctime # 建立 Thread 的例項,傳給它一個可呼叫的類例項 loops = [3, 2, 1, 1, 1] class ThreadFunc(object): def __init__(self, func, args, name=''): self.name = name self.func = func self.args = args def __call__(self): self.func(*self.args) def loop(i, nsec): print(f'start loop {i} at: {ctime()}') sleep(nsec) print(f'end loop {i} at: {ctime()}') def main(): print('start at', ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = threading.Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__)) threads.append(t) for i in nloops: # start threads threads[i].start() for i in nloops: # wait for all threads[i].join() # threads to finish print(f'all done at: {ctime()}') if __name__ == '__main__': main()
-
派生 Thread 的子類,並建立子類的例項。
import threading from time import sleep, ctime # 建立 Thread 的例項,傳給它一個可呼叫的類例項 # 子類的建構函式必須先呼叫其基類的建構函式 # 特殊方法__call__()在 子類中必須要寫為 run() loops = [3, 2, 1, 1, 1] class MyThread(threading.Thread): def __init__(self, func, args, name=''): threading.Thread.__init__(self) self.name = name self.func = func self.args = args def run(self): self.func(*self.args) def loop(i, nsec): print(f'start loop {i} at: {ctime()}') sleep(nsec) print(f'end loop {i} at: {ctime()}') def main(): print('start at', ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = MyThread(loop, (i, loops[i]), loop.__name__) threads.append(t) for i in nloops: # start threads threads[i].start() for i in nloops: # wait for all threads[i].join() # threads to finish print(f'all done at: {ctime()}') if __name__ == '__main__': main()
使用鎖
python和java一樣,也具有鎖機制,而且建立與使用鎖都是很簡便的。
一般在多執行緒程式碼中,總會有一些特 定的函式或程式碼塊不希望(或不應該)被多個執行緒同時執行,通常包括修改資料庫、更新檔案或 其他會產生競態條件的類似情況
鎖有兩種狀態:鎖定和未鎖定。而且它也只支援兩個函式:獲得鎖和釋放鎖。
一般鎖的呼叫如下
# 載入執行緒的鎖物件 lock = threading.Lock() # 獲取鎖 lock.acquire() # ...程式碼 # 釋放鎖 lock.release()
更簡潔的方法是使用with關鍵字,如下程式碼功能同上
# 載入執行緒的鎖物件 lock = threading.Lock() with lock : #...程式碼
示例程式碼:
import threading from time import sleep, ctime lock = threading.Lock() def a(): lock.acquire() for x in range(5): print(f'a:{str(x)}') sleep(0.01) lock.release() def b(): lock.acquire() for x in range(5): print(f'a:{str(x)}') sleep(0.01) lock.release() threading.Thread(target=a).start() threading.Thread(target=b).start()
相關屬性和方法
-
Thread物件的屬性
屬性 | 描述 |
---|---|
name | 執行緒名 |
ident | 執行緒的識別符號 |
daemon | 布林標誌,表示這個執行緒是否是守護執行緒 |
-
Thread物件的方法
方法 | 描述 |
---|---|
init(group=None, tatget=None, name=None, args=(), kwargs ={}, verbose=None, daemon=None) | 例項化一個執行緒物件,需要有一個可呼叫的 target,以及其引數 args 或 kwargs。還可以傳遞 name 或 group 引數,不過後者還未實現。此 外 , verbose 標 志 也 是 可 接 受 的 。 而 daemon 的 值 將 會 設 定 thread.daemon 屬性/標誌 |
start() | 開始執行該執行緒 |
run() | 定義執行緒功能的方法(通常在子類中被應用開發者重寫) |
join (timeout=None) | 直至啟動的執行緒終止之前一直掛起;除非給出了 timeout(秒),否則 會一直阻塞 |
is_alive() | 布林標誌,表示這個執行緒是否還存活 |
-
threading模組其他函式
函式 | 描述 |
---|---|
start() | 開始執行該執行緒 |
active_count() | 當前活動的 Thread 物件個數 |
enumerate() | 返回當前活動的 Thread 物件列表 |
settrace(func) | 為所有執行緒設定一個 trace 函式 |
setprofile (func) | 為所有執行緒設定一個 profile 函式 |
stack_size(size=0) | 返回新建立執行緒的棧大小;或為後續建立的執行緒設定棧的大小 為 size |
Lock() | 載入執行緒的鎖物件,是一個基本的鎖物件,一次只能一個鎖定,其餘鎖請求,需等待鎖釋放後才能獲取,物件有acquire()和release()方法 |
RLock() | 多重鎖,在同一執行緒中可用被多次acquire。如果使用RLock,那麼acquire和release必須成對出現,呼叫了n次acquire鎖請求,則必須呼叫n次的release才能線上程中釋放鎖物件 |
通常來說,多執行緒是一個好東西。不過由於 Python 的 GIL 的限制,多執行緒更適合於 I/O 密集型應用(I/O 釋放了 GIL,可以允 許更多的併發),而不是計算密集型應用。對於後一種情況而言,為了實現更好的並行性,你需要使用多程序,以便讓 CPU 的其他核心來執行。