10

ここでタイムアウト関数を作成するコードを見つけましたが、これは機能していないようです。完全なテストコードは次のとおりです。

def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
    import threading
    class InterruptableThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
            self.result = None

        def run(self):
            try:
                self.result = func(*args, **kwargs)
            except:
                self.result = default

    it = InterruptableThread()
    it.start()
    it.join(timeout_duration)
    if it.isAlive():
        return default
    else:
        return it.result


def foo():
    while True:
        pass

timeout(foo,timeout_duration=3)

予想される動作:コードは3秒以内に終了します。問題はどこだ?

4

3 に答える 3

13

スレッドは別のスレッドを正常に強制終了できないため、現在のコードでfooは終了しません。(thread.daemon = TruePythonプログラムでは、デーモンスレッドのみが残っていると終了しfooますが、メインスレッドも終了せずに終了することはできません。)

シグナルを使用して実行を停止しようとした人もいますが、これは安全でない場合があります。

変更できる場合fooは、多くの解決策が考えられます。たとえばthreading.Event、whileループから抜け出すかどうかを確認できます。

ただし、変更できない場合は、スレッドとは異なり、サブプロセスを終了できるためfoo、モジュールを使用してサブプロセスで実行できます。multiprocessingこれがどのように見えるかの例です:

import time
import multiprocessing as mp

def foo(x = 1):
    cnt = 1
    while True:
        time.sleep(1)
        print(x, cnt)
        cnt += 1

def timeout(func, args = (), kwds = {}, timeout = 1, default = None):
    pool = mp.Pool(processes = 1)
    result = pool.apply_async(func, args = args, kwds = kwds)
    try:
        val = result.get(timeout = timeout)
    except mp.TimeoutError:
        pool.terminate()
        return default
    else:
        pool.close()
        pool.join()
        return val


if __name__ == '__main__':
    print(timeout(foo, kwds = {'x': 'Hi'}, timeout = 3, default = 'Bye'))
    print(timeout(foo, args = (2,), timeout = 2, default = 'Sayonara'))

収量

('Hi', 1)
('Hi', 2)
('Hi', 3)
Bye
(2, 1)
(2, 2)
Sayonara

これにもいくつかの制限があることに注意してください。

  • サブプロセスは、親プロセスの変数のコピーを受け取ります。サブプロセスで変数を変更しても、親プロセスには影響しません。関数funcで変数を変更する必要がある場合は、共有変数を使用する必要があります。

  • 引数(渡されるargs)とキーワード(kwds)は選択可能でなければなりません。

  • プロセスはスレッドよりもリソースを大量に消費します。通常、マルチプロセッシングプールを作成するのは、プログラムの開始時に1回だけです。このtimeout関数は、Pool呼び出すたびに作成されます。pool.terminate()を終了する必要があったため、これが必要でしたfoo。もっと良い方法があるかもしれませんが、私はそれについて考えていません。
于 2012-12-11T14:53:14.600 に答える
2

デーモンスレッドitに変える必要があります:

it = ...
it.daemon = True
it.start()

それ以外の場合は、ユーザースレッドとして作成され、すべてのユーザースレッドが終了するまでプロセスは停止しません。

実装では、スレッドの待機がタイムアウトした後も、スレッドは引き続き実行され、リソースを消費することに注意してください。CPythonのグローバルインタープリターロックは、問題をさらに悪化させる可能性があります。

于 2012-12-11T13:13:46.273 に答える
2

使用する利点multiprocessingは、プロセスがメモリを共有せず、子で発生することはすべてその関数に限定されたままであり、他のプロセスが終了することはありません。子プロセスに3秒のタイムアウトを追加する最も簡単な方法は次のとおりです。

import multiprocessing

def my_child():
    function here

process = multiprocessing.Process(target=my_child)
process.daemon = True
process.start()

process.join(3)
if process.is_alive():
    process.terminate()
于 2020-07-06T09:14:55.470 に答える