Python Event實現執行緒通訊

2020-07-16 10:04:45
Event 是一種非常簡單的執行緒通訊機制,一個執行緒發出一個 Event,另一個執行緒可通過該 Event 被觸發。

Event 本身管理一個內部旗標,程式可以通過 Event 的 set() 方法將該旗標設定為 True,也可以呼叫 clear() 方法將該旗標設定為 False。程式可以呼叫 wait() 方法來阻塞當前執行緒,直到 Event 的內部旗標被設定為 True。

Event 提供了如下方法:
  • is_set():該方法返回 Event 的內部旗標是否為True。
  • set():該方法將會把 Event 的內部旗標設定為 True,並喚醒所有處於等待狀態的執行緒。
  • clear():該方法將 Event 的內部旗標設定為 False,通常接下來會呼叫 wait() 方法來阻塞當前執行緒。
  • wait(timeout=None):該方法會阻塞當前執行緒。

下面程式示範了 Event 最簡單的用法:
import threading
import time

event = threading.Event()
def cal(name):
    # 等待事件,進入等待阻塞狀態
    print('%s 啟動' % threading.currentThread().getName())
    print('%s 準備開始計算狀態' % name)
    event.wait()    # ①
    # 收到事件後進入執行狀態
    print('%s 收到通知了.' % threading.currentThread().getName())
    print('%s 正式開始計算!'% name)
# 建立並啟動兩條,它們都會①號程式碼處等待
threading.Thread(target=cal, args=('甲', )).start()
threading.Thread(target=cal, args=("乙", )).start()
time.sleep(2)    #②
print('------------------')
# 發出事件
print('主執行緒發出事件')
event.set()
上面程式以 cal() 函數為 target,建立並啟動了兩個執行緒。由於 cal() 函數在 ① 號程式碼處呼叫了 Event 的 wait(),因此兩個執行緒執行到 ① 號程式碼處都會進入阻塞狀態;即使主執行緒在 ② 號程式碼處被阻塞,兩個子執行緒也不會向下執行。

直到主程式執行到最後一行,程式呼叫了 Event 的 set() 方法將 Event 的內部旗標設直為 True,並喚醒所有等待的執行緒,這兩個執行緒才能向下執行。

執行上面程式,將看到如下輸出結果:

Thread-1 啟動
甲 準備開始計算狀態
Thread-2 啟動
乙 準備開始計算狀態
------------------
主執行緒發出事件
Thread-1 收到通知了.
Thread-2 收到通知了.
甲 正式開始計算!
乙 正式開始計算!

上面程式還沒有使用 Event 的內部旗標,如果結合 Event 的內部旗標,同樣可實現前面的 Account 的生產者-消費者效果:存錢執行緒(生產者)存錢之後,必須等取錢執行緒(消費者)取錢之後才能繼續向下執行。

Event 實際上優點類似於 Condition 和旗標的結合體,但 Event 本身並不帶 Lock 物件,因此如果要實現執行緒同步,還需要額外的 Lock 物件。

下面是使用 Event 改寫後的 Account:
import threading

class Account:
    # 定義構造器
    def __init__(self, account_no, balance):
        # 封裝賬戶編號、賬戶餘額的兩個成員變數
        self.account_no = account_no
        self._balance = balance
        self.lock = threading.Lock()
        self.event = threading.Event()
    # 因為賬戶餘額不允許隨便修改,所以只為self._balance提供getter方法
    def getBalance(self):
        return self._balance
    # 提供一個執行緒安全的draw()方法來完成取錢操作
    def draw(self, draw_amount):
        # 加鎖
        self.lock.acquire()
        # 如果Event內部旗標為True,表明賬戶中已有人存錢進去
        if self.event.is_set():
            # 執行取錢操作
            print(threading.current_thread().name
                + " 取錢:" +  str(draw_amount))
            self._balance -= draw_amount
            print("賬戶餘額為:" + str(self._balance))
            # 將Event內部旗標設為False
            self.event.clear()
            # 釋放加鎖
            self.lock.release()
            # 阻塞當前執行緒阻塞
            self.event.wait()
        else:
            # 釋放加鎖
            self.lock.release()
            # 阻塞當前執行緒阻塞
            self.event.wait()
    def deposit(self, deposit_amount):
        # 加鎖
        self.lock.acquire()
        # 如果Event內部旗標為False,表明賬戶中還沒有人存錢進去
        if not self.event.is_set():
            # 執行存款操作
            print(threading.current_thread().name
                + " 存款:" +  str(deposit_amount))
            self._balance += deposit_amount
            print("賬戶餘額為:" + str(self._balance))
            # 將Event內部旗標設為True
            self.event.set()
            # 釋放加鎖
            self.lock.release()
            # 阻塞當前執行緒阻塞
            self.event.wait()
        else:
            # 釋放加鎖
            self.lock.release()
            # 阻塞當前執行緒阻塞
            self.event.wait()