3

これは何度も何度も議論されてきましたが、これを達成する最善の方法についてはまだよく理解していません.

メイン アプリ スレッドとワーカー スレッドの 2 つのスレッドがあるとします。メイン アプリ スレッド (WXWidgets GUI スレッド、またはループしてコンソールでユーザー入力を受け入れるスレッドなど) には、ワーカー スレッドを停止する理由がある可能性があります - ユーザーがアプリケーションを閉じた、停止ボタンがクリックされた、何らかのエラーが発生したメインスレッドで発生しました。

一般的に提案されているのは、スレッドが終了するかどうかを判断するために頻繁にチェックするフラグを設定することです。ただし、これにアプローチするための提案された方法には2つの問題があります。

まず、フラグの定期的なチェックをコードに書き込むと、コードが非常に見苦しくなります。また、大量のコードの重複により、問題が発生しやすくなります。次の例を見てください。

def WorkerThread():

    while (True):
        doOp1() # assume this takes say 100ms.
        if (exitThread == True): 
            safelyEnd()
            return
        doOp2() # this one also takes some time, say 200ms
        if (exitThread == True): 
            safelyEnd()
            return
        if (somethingIsTrue == True):
            doSomethingImportant()
            if (exitThread == True): return
            doSomethingElse()
            if (exitThread == True): return 
        doOp3() # this blocks for an indeterminate amount of time - say, it's waiting on a network respond
        if (exitThread == True): 
            safelyEnd()
            return
        doOp4() # this is doing some math
        if (exitThread == True): 
            safelyEnd()
            return
        doOp5() # This calls a buggy library that might block forever.  We need a way to detect this and kill this thread if it's stuck for long enough...
        saveSomethingToDisk() # might block while the disk spins up, or while a network share is accessed...whatever
        if (exitThread == True): 
            safelyEnd()
            return


def safelyEnd():
    cleanupAnyUnfinishedBusiness() # do whatever is needed to get things to a workable state even if something was interrupted
    writeWhatWeHaveToDisk() # it's OK to wait for this since it's so important

コードを追加したりコードを変更したりする場合は、それらのチェック ブロックをあちこちに追加していることを確認する必要があります。ワーカー スレッドが非常に長いスレッドである場合、数十または数百ものチェックを簡単に行うことができます。非常に面倒です。

他の問題を考えてみてください。doOp4() が誤ってデッドロックした場合、アプリは永遠にスピンし、終了しません。ユーザーエクスペリエンスが良くない!

safelyEnd()デーモン スレッドを使用することも、コードを実行する機会を奪ってしまうため、あまり良い選択肢ではありません。このコードは、ディスク バッファのフラッシュ、デバッグ目的でのログ データの書き込みなど、重要な場合があります。

2 つ目は、頻繁にチェックする機会がない場所をブロックする関数をコードが呼び出す可能性があることです。この関数は存在しますが、アクセスできないコード内にあるとしましょう-ライブラリの一部としましょう:

def doOp4():
    time.sleep(60) # imagine that this is a network thread, that waits for 60 seconds for a reply before returning.

そのタイムアウトが 60 秒の場合、たとえメイン スレッドがスレッドを終了するようにシグナルを送ったとしても、ネットワーク応答の待機を停止して終了することが完全に合理的な場合でも、メイン スレッドは 60 秒間そこに留まる可能性があります。ただし、そのコードが私が作成したものではないライブラリの一部である場合、それがどのように機能するかを制御することはできません。

ネットワーク チェック用のコードを書いたとしても、基本的にリファクタリングして、60 秒待つのではなく、60 回ループして 1 秒待ってから終了スレッドをチェックするようにする必要があります。繰り返しますが、非常に面倒です!

これらすべての結果として、これを簡単に実装できる良い方法は、特定のスレッドで例外を発生させることです。それができれば、ワーカー スレッドのコード全体を try ブロックでラップし、そのsafelyEnd()コードを例外ハンドラーまたはブロックに入れることができfinallyます。

これを達成する方法、または物事を機能させる別の手法でこのコードをリファクタリングする方法はありますか? 理想的には、ユーザーが終了を要求したときに、可能な限り最小限の時間を待たせたいということです。これはアプリでは非常に一般的なことなので、これを達成するための簡単な方法が必要なようです!

スレッド通信オブジェクトのほとんどは、このタイプのセットアップを許可していません。それらは終了フラグを持つためのよりクリーンな方法を可能にするかもしれませんが、それでもその終了フラグを常にチェックする必要がなくなるわけではなく、外部呼び出しまたは単に忙しいループ。

私にとって最大の問題は、長いワーカー スレッド プロシージャがある場合、何百ものフラグ チェックを散らかさなければならないことです。これはあまりにも面倒で、あまり良いコーディング方法とは思えません。もっと良い方法があるはずです...

アドバイスをいただければ幸いです。

4

2 に答える 2

1

コードの実行速度が約 10 倍遅くなることを気にしない場合は、Thread2以下に実装されたクラスを使用できます。次の例は、新しいstopメソッドを呼び出すと、次のバイトコード命令でスレッドを強制終了する方法を示しています。クリーンアップ システムの実装は、読者の課題として残されています。

import threading
import sys

class StopThread(StopIteration): pass

threading.SystemExit = SystemExit, StopThread

class Thread2(threading.Thread):

    def stop(self):
        self.__stop = True

    def _bootstrap(self):
        if threading._trace_hook is not None:
            raise ValueError('Cannot run thread with tracing!')
        self.__stop = False
        sys.settrace(self.__trace)
        super()._bootstrap()

    def __trace(self, frame, event, arg):
        if self.__stop:
            raise StopThread()
        return self.__trace


class Thread3(threading.Thread):

    def _bootstrap(self, stop_thread=False):
        def stop():
            nonlocal stop_thread
            stop_thread = True
        self.stop = stop

        def tracer(*_):
            if stop_thread:
                raise StopThread()
            return tracer
        sys.settrace(tracer)
        super()._bootstrap()

################################################################################

import time

def main():
    test = Thread2(target=printer)
    test.start()
    time.sleep(1)
    test.stop()
    test.join()

def printer():
    while True:
        print(time.time() % 1)
        time.sleep(0.1)

if __name__ == '__main__':
    main()

このThread3クラスは、クラスよりも約 33% 高速にコードを実行するようですThread2

于 2014-09-05T14:59:09.663 に答える