2013-01-08 98 views
2

我有一個正在運行的線程即將終止,但在那一點上,我已經想擺脫它的引用。我能否將它踢出去並希望最好,還是應該以某種特殊方式處理它?如何處理線程即將終止?

class CoolThing(object): 

    def __init__(self): 
     self.thread = None 

    def run_in_background(self, callback, period=0.5): 
     if self.thread: 
      raise RuntimeError 

     def worker(): 
      worker.running = True 
      while worker.running: 
       if some_event(): 
        callback(self) 
       time.sleep(period) 

     self.thread = (threading.Thread(target=worker), worker) 
     self.thread[0].start() 

    def stop_background(self, join=False): 
     if not self.thread: 
      raise RuntimeError 

     # Make the worker function end harmfully. 
     self.thread[1].running = False 

     if join: 
      self.thread[0].join() 

     # What should I now do with the thread being about to 
     # terminate, when not being joined? 

     # ... 

     self.thread = None 
+0

我的第一反應是'self.thread [0]。加入() ',但這似乎並沒有殺死線程,它只是等待線程完成執行...... – mgilson

+0

@mgilson:對。我不希望'stop_background()'在沒有明確請求的情況下被阻塞(見編輯)。 –

回答

4

當while循環留下您應該設置線程沒有在從worker內誘發的回調,:

編輯:現在還支持後臺程序立即重啓

import time 
import threading 

class CoolThing(object): 

    def __init__(self): 
     self.thread = None 

    def run_in_background(self, callback, period=0.5): 
     wait_count = 0 
     while True: 
      if self.thread: 
       if self.thread[1].running or wait_count>10: 
        raise RuntimeError() 
       time.sleep(0.5) 
       wait_count += 1 
      else: 
       break 

     def worker(): 
      t0 = time.time() 
      worker.running = True 
      while worker.running: 
       if time.time()-t0>2: 
        callback() 
        t0 = time.time() 
       time.sleep(period) 
      worker.callback() 

     worker.callback = self.dispose 
     self.thread = (threading.Thread(target=worker), worker) 
     self.thread[0].start() 

    def stop_background(self, join=False): 
     if not self.thread: 
      raise RuntimeError 
     self.thread[1].running = False 
     if join: 
      self.thread[0].join() 
     self.stopping = True 

    def dispose(self): 
     self.thread = None 
     self.stopping 

def my_callback(): 
    print "Beep" 

if __name__=="__main__": 
    cool_thing = CoolThing() 
    cool_thing.run_in_background(my_callback, 0.5) 
    time.sleep(10) 
    cool_thing.stop_background() 
    # Immediatley restart process 
    cool_thing.run_in_background(my_callback, 0.5) 
    time.sleep(10) 
    cool_thing.stop_background() 
    print cool_thing.thread 
    time.sleep(3) 
    print cool_thing.thread 

給出了輸出:

Beep 
Beep 
Beep 
(<Thread(Thread-2, started 10760)>, <function worker at 0x02DEDD70>) 
None 

因此,在調用stop_background之後,self.thread仍然被設置,但後來它是None。您也可以保存worker.callback變量並通過它的名稱調用dispose(),但這樣,代碼更加靈活。

編輯2:新要求,新的代碼示例

我做了一個單獨的類工人(SRP)和CoolThing舉辦這樣的員工名單中。如果啓動了run_background(...),它將檢查是否有任何工作者仍在運行(不停止請求),然後引發RuntimeError。否則,開始一個新的工作。 stop_background()告訴每個工人停下來,每個工人調用一個回調,然後將這個工人從所有工人的清單中刪除。

import time 
import threading 

class Worker(threading.Thread): 
    def __init__(self, callback, period=0.5, finished_callback = None): 
     threading.Thread.__init__(self) 
     self.callback = callback 
     self.period = period 
     self._stop_requested = False 
     self._finished_callback = finished_callback 

    def run(self): 
     t0 = time.time() 
     while not self._stop_requested: 
      if time.time()-t0>2: 
       self.callback() 
       t0 = time.time() 
      time.sleep(self.period) 
     if self._finished_callback: 
      self._finished_callback(self) 

    def request_stop(self): 
     self._stop_requested = True 

    @property 
    def stopping(self): 
     return self._stop_requested 

class CoolThing(object): 

    def __init__(self): 
     self.workers = [] 
     self.workers_lock = threading.Lock() 

    def run_in_background(self, callback, period=0.5): 
     if len([w for w in self.workers if not w.stopping])>0: 
      raise RuntimeError() 
     worker = Worker(callback, period, finished_callback=self.dispose) 
     with self.workers_lock: 
      self.workers.append(worker) 
     worker.start() 

    def stop_background(self, join=False): 
     if len(self.workers) == 0: 
      raise RuntimeError() 
     for worker in self.workers: 
      worker.request_stop() 
     if join: 
      for worker in self.workers: 
       worker.join() 

    def dispose(self, worker): 
     with self.workers_lock: 
      self.workers.remove(worker) 

def my_callback(): 
    print "Beep" 

if __name__=="__main__": 
    cool_thing = CoolThing() 
    cool_thing.run_in_background(my_callback, 0.5) 
    time.sleep(10) 
    print cool_thing.workers 
    cool_thing.stop_background() 
    # Immediatley restart process 
    cool_thing.run_in_background(my_callback, 0.5)  
    print cool_thing.workers 
    time.sleep(5) 
    print cool_thing.workers 
    time.sleep(5) 
    cool_thing.stop_background() 
    print cool_thing.workers 
    time.sleep(3) 
    print cool_thing.workers 
+0

感謝您的回答。我立即看到:在調用stop_background()後,我無法成功地調用'run_in_background()',因爲'thread'仍然會被設置並且會引發'RuntimeError'。你也有這個解決方案嗎? –

+0

當然。將調整我的樣本。 –

+0

調整後的樣本包含此功能。如果線程正在停止,但尚未爲空,則會重試十次(中間有睡眠)。我避免使用鎖,因爲它可能會阻止您的代碼。僵局是嚴重的問題。 –

0

作爲一個評論者已經提到的,你不能因爲所有做的就是等待線程終止自然使用join()。我不知道任何本地Python API強制終止線程,但底層線程庫通常會有一個(例如pthread_kill())。這通常是因爲強行殺死Python中的線程通常是一件非常糟糕的事情。

但是,在這種情況下,您並未顯示您想要殺死一個不合作的線程,您已向您希望它正常終止的線程發出信號(通過設置running屬性)。

我看不到任何理由,你不能直接設置threadNone - 我不相信刪除線程對象的最後一個引用實際上會導致任何問題,因爲線程將保持活動狀態直到它終止(除非你已經設置了daemonTrue並且主程序終止)。當然,定義worker()函數時創建的閉包中的任何對象仍然存在,所以它們不會被釋放,直到線程終止(並且可能甚至不是閉包設置的方式 - 我需要仔細想一想)。

無論如何,我認爲你的生活會更容易,如果你在線程結束時安排join()。如果您關注的是,你將不得不等待你的線程的一段時間了,你可以使用一個threading.Condition對象要解決這個問題:

class CoolThing(object): 

    def __init__(self): 
     self.thread = None 
     self.exit_condition = threading.Condition() 

    def run_in_background(self, callback, period=0.5): 
     if self.thread: 
      raise RuntimeError 

     def worker(exit_condition): 
      exit_condition.acquire() 
      worker.running = True 
      while worker.running: 
       if some_event(): 
        callback(self) 
       exit_condition.wait(period) 
      exit_condition.release() 

     self.thread = (threading.Thread(target=worker, args=(self.exit_condition,)), 
         worker) 
     self.thread[0].start() 

    def stop_background(self): 
     if not self.thread: 
      raise RuntimeError 

     # Make the worker function end harmfully. 
     self.exit_condition.acquire() 
     self.thread[1].running = False 
     self.exit_condition.notify() 
     self.exit_condition.release() 

     self.thread[0].join() 
     self.thread = None