1. 程式人生 > >python使用多執行緒

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 的其他核心來執行。