問題背景
最近在開發一款自動化的應用,其中有一個自動化任務會由下面這三個按鈕控制:
邏輯也很簡單,我大概畫下圖就是這樣的:
但是,在測試時,卻發現了問題:
當我點選暫停任務後,此時子執行緒被阻塞。如果我這個時候點選停止,那麼就會任務結束。
之後,如果我再點選開始執行,整個應用就會卡死,非常離譜。
以下是簡化後的程式碼:
import threading import time from sample_singleton import singleton
@singleton class TestThreadingEvent: def init(self): self._stop_event = threading.Event() self._pause_event = threading.Event() self._thread = None
def set_stop(self): self._stop_event.set() def set_pause(self): self._pause_event.set() def start(self): print("任務開始") self._stop_event.clear() self._pause_event.set() print("開始執行") self._thread = threading.Thread(target=self._run) self._thread.start() def _run(self): count = 0 while True: if self._stop_event.is_set(): print("任務被成功停止") return print(f"是否需要暫停:{not self._pause_event.is_set()}") self._pause_event.wait() # 執行任務 print(f"do something: {count}") time.sleep(1) count += 1 def pause(self): print("點選了暫停") self._pause_event.clear() time.sleep(2) def stop(self): print("點選了停止") self._stop_event.set() print("成功停止") if self._thread is not None: self._thread.join() # 確保執行緒終止
按照程式碼邏輯,我期待的結果是點選停止後,再次點選開始就可以開始重新執行,但是,雖然第一次顯示停止了,可如果想再次開始,程式就會卡住不動了,下面為測試時輸出的結果:
我點選了開始 任務開始 開始執行 是否需要暫停:False do something: 0 我點選了暫停 是否需要暫停:True 我點選了停止 成功停止 # 然後在這裏卡死
昨天下午一直在程式碼中斷點找原因,搞了半天,都沒能解決,下班前我甚至都在懷疑是不是 Python 程式碼的問題,想去看看原始碼找原因了。
找出原因
不過簡化程式碼,確實比較有效,當我把整個流程簡化成上面的程式碼,就比較方便找出問題出在哪裏了。
是因為,當我點選“暫停”後,子執行緒進入阻塞狀態。當執行“停止”操作時,使用了 self._thread.join()
,這會導致主執行緒阻塞,直到子執行緒 self._thread
完成。然而,如果子執行緒因阻塞狀態無法完成,就會導致主執行緒永久等待,結果是主執行緒卡死。
後面我看了下我們實際開發的應用程式碼,問題要更復雜點,但說到底,都是執行緒阻塞狀態沒有得到正確處理,導致的卡死。
解決辦法
由於主執行緒卡死是因為子執行緒的阻塞狀態造成的,可以透過以下兩種方法解決:
處理子執行緒阻塞:引入超時控制,確保子執行緒在合理時間內完成任務,並在必要時修改子執行緒的阻塞狀態,以避免主執行緒長時間等待。
銷燬子執行緒:如果子執行緒在完成任務後不再需要重複使用,可以考慮在結束時直接銷燬該執行緒,以避免阻塞主執行緒。
這兩種方法可以有效避免主執行緒因子執行緒阻塞而卡死的問題。