程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Python >> Python 不支持殺死子線程

Python 不支持殺死子線程

編輯:Python

昨天為我的 casnet 程序添加新功能。其中一個功能是斷線自動重連,本來是單線程的程序,添加這個功能就需要後台有一個線程定時地查詢當前狀態,如果掉線就自動重連。因之遇到了一個如何設計這個守護線程的問題。

我剛開始的想法是後台線程每次運行查詢後 sleep 一段時間,然後再運行查詢。但是我馬上遇到了一個問題:當主程序退出時,後台線程仍在運行,主窗口無法退出。

在使用其它的庫時,比如 POSIX 的 pthread,可以使用 ptread_cancel(tid) 在主線程中結束子線程。但是 Python 的線程庫不支持這樣做,理由是我們不應該強制地結束一個線程,這樣會帶來很多隱患,應該讓該線程自己結束自己。所以在 Python 中,推薦的一種方法是在子線程中循環判斷一個標志位,在主線程中改變該標志位,子線程讀到標志位改變,就結束自己。

import threading

class X(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    self.flag = 1

  def run(self):
    while self.flag == 1:
      sleep(300)
      ...

如果直接使用這種方法,那麼我前面的設計就會出現問題。因為線程會被 sleep 阻塞一段時間,那麼只有在 sleep 的間隙,才有可能去讀取標志位。這樣主線程需要等待當前 sleep 結束才能使子線程退出,進而整個程序才能退出。這種做法是行不通的,你不可能指望用戶點擊“關閉窗口”後等待幾百秒程序才能退出。

當然,也可以使用系統命令 kill 來殺死整個進程。但問題是這樣做既不 graceful,又不能保證代碼對不同系統的兼容性。

只好換個思路,從原來後台進程的設計改起。定時執行未必非得使用 sleep,也可以像 crontab 那樣判斷當前時間能不能整除某個值,但這樣做不能保證任務在某個時間間隔內只執行一次,因為除數的精度和任務的執行時間不好把握;或者使用 timer,但是 timer 會帶來更多線程,增加了復雜度。

於是最後決定使用解決 Feedbuner 圖標定時抓取問題的方法。在線程中保存上次查詢時間,比較當前時間與上次查詢時間的差,若大於某個值,就進行查詢並更新保存的時間。

  def run(self):
    self.last = time.time()
    while self.flag == 1:
      Now = time.time()
      if Now - self.last > 300:
         self.last = Now
         ...

這樣就既能保證子線程在 flag 改變之後盡快退出,又能保證在指定時間間隔內任務只運行一次。但是網友 earthengine 兄指出這種方法並不妥,代碼中不用 sleep 就變成了忙循環,這樣會造成 CPU 使用率過高的問題,僅僅在循環中間添加一個 sleep(0~1) 就能大幅度地降低 CPU 使用,而且關閉程序時 1 秒鐘以內的延遲對於用戶來說一般還是可以接受的。

  def run(self):
    self.last = time.time()
    while self.flag == 1:
      sleep(1)
      Now = time.time()
      if Now - self.last > 300:
         self.last = Now
         ...

再深入思考一下,雖然本文中的後台線程從功能上來看似乎用不著考慮太多同步的問題,但最後的退出過程可視為一個線程同步的過程。因此可以采用線程同步的思想來設計後台線程:在正常工作時,後台線程進行帶超時的等待,超時後就執行工作;退出時主線程給後台線程發送一個信號,由於後台線程在超時等待,因此接收信號後就終止退出。這樣,在用戶結束程序時,就不用等待 sleep 到時了。

import threading

class X(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    self.flag = 1
    self.cond = threading.Condition()

  def run(self):
    self.cond.acquire()
    self.condition.wait(300)
    while self.flag == 1:
      ...
      self.cond.release()
      self.cond.acquire()
      self.condition.wait(300)

...
x.flag = 0
x.cond.acquire()
x.cond.notify()
x.cond.release()

最後,非常感謝 earthengine 兄的精彩評論,小弟受益良多。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved