2018-01-02 pythonスレッドを優雅に終了する方法

6396 ワード

前言・零
pythonでスレッドを終了するには、通常の方法は--->フラグまたはロック方式を設定/チェックすることです.
この方式はいいですか.
あまりよくないはずだ.すべてのプログラム言語で、突然スレッドが終了するので、これはどうしても良い設計モードではありません.
同時に
次のような場合があります.
  • スレッドは、適切に閉じる必要がある臨界リソースを開く場合、例えば、読み取り可能なファイルを開く場合.
  • スレッドはすでにいくつかの他のスレッドを作成しており、これらのスレッドも閉じる必要があります(これは子孫スレッドが遊離するリスクがあるでしょう!).

  • 簡単に言えば、私たちの大きなグループのスレッドが共通のリソースを共有しています.あなたはその中の1つのスレッドを「場から離れる」必要があります.もしこのスレッドがちょうどリソースを占有していたら、強制的に離れるのは結局リソースがロックされて死んで、みんなが手に入れられません.どのように少し修仙類の小説の筋に似ているのではありませんか!
    なぜthreadingはstartだけでendがないのか知っていますか?
    ほら、スレッドは一般的にネットワーク接続、システムリソースの解放、dumpストリームファイルに使われています.これらはIOに関連しています.突然スレッドを閉じたら、合理的に閉じていません.どうすればいいですか.自分にバグを作ったのではないでしょうか.あ?!
    そのため、このようなことの中で最も重要なのはスレッドを終了することではなく、スレッドのクリーンアップでしょう.
    ソリューション・壹
    niceを比較する方法は、スレッドごとに終了要求フラグを持って、スレッドの中で一定の時間間隔を置いてチェックして、自分が離れるべきかどうかを見ることです.
    import threading
    
    class StoppableThread(threading.Thread):
        """Thread class with a stop() method. The thread itself has to check
        regularly for the stopped() condition."""
    
        def __init__(self):
            super(StoppableThread, self).__init__()
            self._stop_event = threading.Event()
    
        def stop(self):
            self._stop_event.set()
    
        def stopped(self):
            return self._stop_event.is_set()
    

    このコードに示すように、スレッドを終了したい場合はstop()関数を呼び出し、join()関数を使用してスレッドが適切に終了するのを待つ必要があります.スレッドは周期的に停止フラグを検出しなければならない.
    しかし、いくつかの使用シーンでは、killがスレッドを削除する必要があります.たとえば、外部ライブラリをカプセル化していますが、この外部ライブラリは長時間呼び出されているので、このプロセスを中断したいと思っています.
    解決策·弐
    次のシナリオはpythonスレッドの中でraiseのExceptionを許可することです(もちろんいくつかの制限があります).
    def _async_raise(tid, exctype):
        '''Raises an exception in the threads with id tid'''
        if not inspect.isclass(exctype):
            raise TypeError("Only types can be raised (not instances)")
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
                                                      ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            # "if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
            raise SystemError("PyThreadState_SetAsyncExc failed")
    
    class ThreadWithExc(threading.Thread):
        '''A thread class that supports raising exception in the thread from
           another thread.
        '''
        def _get_my_tid(self):
            """determines this (self's) thread id
    
            CAREFUL : this function is executed in the context of the caller
            thread, to get the identity of the thread represented by this
            instance.
            """
            if not self.isAlive():
                raise threading.ThreadError("the thread is not active")
    
            # do we have it cached?
            if hasattr(self, "_thread_id"):
                return self._thread_id
    
            # no, look for it in the _active dict
            for tid, tobj in threading._active.items():
                if tobj is self:
                    self._thread_id = tid
                    return tid
    
            # TODO: in python 2.6, there's a simpler way to do : self.ident
    
            raise AssertionError("could not determine the thread's id")
    
        def raiseExc(self, exctype):
            """Raises the given exception type in the context of this thread.
    
            If the thread is busy in a system call (time.sleep(),
            socket.accept(), ...), the exception is simply ignored.
    
            If you are sure that your exception should terminate the thread,
            one way to ensure that it works is:
    
                t = ThreadWithExc( ... )
                ...
                t.raiseExc( SomeException )
                while t.isAlive():
                    time.sleep( 0.1 )
                    t.raiseExc( SomeException )
    
            If the exception is to be caught by the thread, you need a way to
            check that your thread has caught it.
    
            CAREFUL : this function is executed in the context of the
            caller thread, to raise an excpetion in the context of the
            thread represented by this instance.
            """
            _async_raise( self._get_my_tid(), exctype )
    

    注釈に記載されているように、これは「妙薬」ではありません.python解釈器の外でスレッドがbusyしていると、端末が異常につかめないからです.
    このコードの合理的な使用方法は、スレッドに特定の異常を捕まえてクリーンアップ操作を実行させることです.これにより、タスクを終了し、適切にクリアすることができます.
    ソリューション・叁
    もし私たちが何かをしなければならないなら、中断のような方法で、私たちはthreadを使うことができます.join方式.
    join                    ,             ,               join  。
    
         :
    
    1.      ,            。
    
    2.     join    ,        join  ,               。
    
    3.    ,         ,           join。
    
    4.   timeout        ,  timeout=2         2s   ,     ,         。
    
    # coding: utf-8
    #    join
    import threading, time  
    def doWaiting1():  
        print 'start waiting1: ' + time.strftime('%H:%M:%S') + "
    " time.sleep(3) print 'stop waiting1: ' + time.strftime('%H:%M:%S') + "
    " def doWaiting2(): print 'start waiting2: ' + time.strftime('%H:%M:%S') + "
    " time.sleep(8) print 'stop waiting2: ', time.strftime('%H:%M:%S') + "
    " tsk = [] thread1 = threading.Thread(target = doWaiting1) thread1.start() tsk.append(thread1) thread2 = threading.Thread(target = doWaiting2) thread2.start() tsk.append(thread2) print 'start join: ' + time.strftime('%H:%M:%S') + "
    " for tt in tsk: tt.join() print 'end join: ' + time.strftime('%H:%M:%S') + "
    "

    デフォルトjoin方式、すなわちパラメータなし、ブロックモードでは、サブスレッドが実行されてから他のものが実行されます.
    1、2つのスレッドが同じ時間に開き、join関数が実行されます.
    2、waiting 1スレッドが3 s実行(待機)した後、終了します.
    3、waiting 2スレッドが8 s実行(待機)した後、実行は終了します.
    4、join関数(メインプロセスに戻った)の実行が終了します.
    ここではデフォルトのjoin方式で、スレッドが走り始めた後にjoinするので、注意してください.joinの後、メインスレッドはサブスレッドが終わるのを待ってからメインラインに戻らなければなりません.
    joinのパラメータ、すなわちtimeoutパラメータを2、すなわちjoin(2)に変更すると、結果は以下のようになる.
  • の2つのスレッドが同じ時間にオンになり、join関数が実行されます.
  • wating 1スレッドは、実行(待機)3秒後に完了する.
  • join終了(2つの2 s、合計4 s、36-32=4、間違いなし).
  • waiting 2スレッドはjoinで規定された待ち時間内(4 s)に完了していないため、自分で後に完了を実行する.

  • join(2)は:私はあなたにサブスレッドを2秒あげて、すべての2 s時計が終わった後に私は行って、私は少しも心配しません!