2013-02-22 8 views
8

私はtimeoutシグナルマネージャーを持っていますが、信号はメインスレッドでのみ動作するため、マルチスレッドモードでエラーが発生します。スレッドを持つPythonタイムアウトコンテキストマネージャー

def timeout_handler(signum, frame): 
    raise TimeoutException() 

@contextmanager 
def timeout(seconds): 
    old_handler = signal.signal(signal.SIGALRM, timeout_handler) 
    signal.alarm(seconds) 
    try: 
     yield 
    finally: 
     signal.alarm(0) 
     signal.signal(signal.SIGALRM, old_handler) 

私はtimeoutのデコレータの実装を見てきましたが、私はthreading.Threadから派生クラス内yieldを渡す方法がわかりません。私の変種は動作しません。

@contextmanager 
def timelimit(seconds): 
    class FuncThread(threading.Thread): 
     def run(self): 
      yield 

    it = FuncThread()   
    it.start() 
    it.join(seconds) 

    if it.isAlive(): 
     raise TimeoutException() 

答えて

-2

システムコールのタイムアウトは信号で行われます。ほとんどのブロッキングシステムコールは、シグナルが発生したときにEINTRを返します。したがって、アラームを使用してタイムアウトを実装できます。

ほとんどのシステムコールで動作するコンテキストマネージャがあり、時間がかかりすぎるとIOErrorがブロッキングシステムコールから呼び出されます。

import signal, errno 
from contextlib import contextmanager 
import fcntl 

@contextmanager 
def timeout(seconds): 
    def timeout_handler(signum, frame): 
     pass 

    original_handler = signal.signal(signal.SIGALRM, timeout_handler) 

    try: 
     signal.alarm(seconds) 
     yield 
    finally: 
     signal.alarm(0) 
     signal.signal(signal.SIGALRM, original_handler) 

with timeout(1): 
    f = open("test.lck", "w") 
    try: 
     fcntl.flock(f.fileno(), fcntl.LOCK_EX) 
    except IOError, e: 
     if e.errno != errno.EINTR: 
      raise e 
     print "Lock timed out" 
+0

:信号が唯一のメインthread'で動作しますline 'original_handler = signal.signal(signal.SIGALRM、timeout_handler)' – San4ez

+1

OPが述べたように、*シグナルはメインスレッド*でしか動かない。 OPには、代わりに別の解決策が必要です。 –

3

私は、あなたがコンテキストマネージャで提案されているものを行う方法を見ることができないことはできませんyield別のスレッドからの流れ。 私がやることは、タイムアウトのあるインターラクティブなスレッドで関数をラップすることです。ここにはrecipeがあります。

あなたは余分なスレッドを持っており、構文はうまくいきませんが、うまくいくでしょう。

+2

レシピによって記述された割り込み可能なスレッドは、実際には中断されず、実際には実行され続けることに注意してください。 AFAIKでは、非メインのPythonスレッドを中断する信頼できる方法はありません。 – Lethargy

9

コンテキストマネージャによって保護されたコードがループベースの場合は、スレッドの処理方法を処理することを検討してください。別のスレッドを強制終了させるのは一般的に安全ではないので、標準的な方法は、制御スレッドにワーカースレッドに見えるフラグを設定させることです。ワーカースレッドは定期的にそのフラグをチェックし、正常にシャットダウンします。ここでは、タイムアウトと類似した何かを行うことができます方法は次のとおりです。

class timeout(object): 
    def __init__(self, seconds): 
     self.seconds = seconds 
    def __enter__(self): 
     self.die_after = time.time() + self.seconds 
     return self 
    def __exit__(self, type, value, traceback): 
     pass 
    @property 
    def timed_out(self): 
     return time.time() > self.die_after 

ここでは、シングルスレッドの使用方法の例です:

with timeout(1) as t: 
    while True: # this will take a long time without a timeout 
     # periodically check for timeouts 
     if t.timed_out: 
      break # or raise an exception 
     # do some "useful" work 
     print "." 
     time.sleep(0.2) 

とマルチスレッド1:

import thread 
def print_for_n_secs(string, seconds): 
    with timeout(seconds) as t: 
     while True: 
      if t.timed_out: 
       break # or raise an exception 
      print string, 
      time.sleep(0.5) 

for i in xrange(5): 
    thread.start_new_thread(print_for_n_secs, 
          ('thread%d' % (i,), 2)) 
    time.sleep(0.25) 

このアプローチは、より多くの侵入でありますシグナルを使用していますが、任意のスレッドで動作します。

+0

これは可能なアプローチですが、私が得たいと思うほど短くて明確ではありません。あなたの変種はデコレータのような機能でコードをラップする必要がありますが、それは私の新しいアプローチです。ありがとう。 – San4ez

0

私はそれが遅いと知っていますが、私はこれを読んでいるだけですが、独自の署名者/コンテキストマネージャを作成するのはどうですか?私はPythonの初心者ですが、経験豊かな開発者からのフィードバックが大好きです。

これは「ミスターFooz」

class TimeoutSignaller(Thread): 
    def __init__(self, limit, handler): 
     Thread.__init__(self) 
     self.limit = limit 
     self.running = True 
     self.handler = handler 
     assert callable(handler), "Timeout Handler needs to be a method" 

    def run(self): 
     timeout_limit = datetime.datetime.now() + datetime.timedelta(seconds=self.limit) 
     while self.running: 
      if datetime.datetime.now() >= timeout_limit: 
       self.handler() 
       self.stop_run() 
       break 

    def stop_run(self): 
     self.running = False 

class ProcessContextManager: 
    def __init__(self, process, seconds=0, minutes=0, hours=0): 
     self.seconds = (hours * 3600) + (minutes * 60) + seconds 
     self.process = process 
     self.signal = TimeoutSignaller(self.seconds, self.signal_handler) 

    def __enter__(self): 
     self.signal.start() 
     return self.process 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     self.signal.stop_run() 

    def signal_handler(self): 
     # Make process terminate however you like 
     # using self.process reference 
     raise TimeoutError("Process took too long to execute") 

ユースケースからの回答のオフに基づいています。私は `とValueErrorを得た私の第一の変異体のように

with ProcessContextManager(my_proc) as p: 
    # do stuff e.g. 
    p.execute() 
関連する問題