파이썬에서 쓰레드를 죽일 수 있는 방법이 있나요?

조회수 22735회

현재 작동 중인 쓰레드를 플래그나 세마포어 등을 이용하지 않고 죽일 수 있는 방법이 있을까요?

1 답변

  • 좋아요

    0

    싫어요
    채택 취소하기

    일반적으로 갑자기 쓰레드를 죽이는 것은 파이썬에서나 다른 언어에서나 좋지 않은 패턴입니다. 아래와 같은 경우를 생각해보세요 :

    * 쓰레드가 적절히 닫아야 할 중요한 리소스를 사용하고 있는 경우
    * 쓰레드가 함께 죽여야 할 여러 쓰레드를 생성해놓은 경우
    

    쓰레드를 관리하고 있을 때, 이를 해결할 좋은 접근법은 각각의 쓰레드가 일정한 시간 간격으로 스스로를 종료시켜야 하는지를 확인할 exit_request 플래그를 지니고 있는 것 입니다.

    예를 들어 :

    import threading
    
    class StoppableThread(threading.Thread):
        """stop() 매써드를 포함한 Thread 클래스.
        쓰레드 자체에서 정기적으로 stopped() 상태를 체크해야합니다."""
    
        def __init__(self):
            super(StoppableThread, self).__init__()
            self._stop = threading.Event()
    
        def stop(self):
            self._stop.set()
    
        def stopped(self):
            return self._stop.isSet()
    

    이 코드에서 만약 쓰레드를 종료하고 싶다면, stop() 함수를 호출하고 join() 함수를 이용하여 쓰레드가 정상적으로 종료되기를 기다려야 합니다. 그러기 위해선 쓰레드가 일정 시간마다 정지 플래그를 확인해야 하죠.

    그러나 때로는 쓰레드를 당장에 죽여야 할 때가 필요합니다. 오랜 호출 시간으로 busy 상태에 있는 외부 라이브러리를 사용해야 해서 이를 인터럽트하고 싶은 경우가 그 예시 중 하나가 될 수 있습니다.

    아래의 코드는 파이썬 쓰레드에서 (일부 제약 사항이 있지만) 예외를 발생시키도록 만들어줍니다 :

    def _async_raise(tid, exctype):
        '''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:
            # "만약 1보다 큰 숫자를 리턴하면, 문제가 발생합니다.
            # 이 경우, exc=NULL 상태로 다시 호출하여 문제를 해결해야 합니다."
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
            raise SystemError("PyThreadState_SetAsyncExc failed")
    
    class ThreadWithExc(threading.Thread):
        '''다른 쓰레드로 예외를 발생시키는 쓰레드 클래스
        '''
        def _get_my_tid(self):
            """자기 tid를 결정하는 함수
    
            주의 : 이 함수는 해당 인스턴스의 신원을 확인하기 위해
            해당 인스턴스를 호출하는 쓰레드에서 사용되는 함수입니다.
            """
            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: python 2.6에서는 더 단순한 방식이 있음 : self.ident
    
            raise AssertionError("could not determine the thread's id")
    
        def raiseExc(self, exctype):
            """쓰레드 자체에서 주어진 예외 타입을 발생시키기 위한 함수.
    
            만약 시스템콜(time.sleep(), socket.accept(), ...)로 인해
            쓰레드가 busy상태이면, 예외가 무시될 수 있습니다.
    
            만약 예외를 사용하여 쓰레드를 종료시켜야 한다면
            아래의 방법으로 확실히 할 수 있습니다 :
    
                t = ThreadWithExc( ... )
                ...
                t.raiseExc( SomeException )
                while t.isAlive():
                    time.sleep( 0.1 )
                    t.raiseExc( SomeException )
    
            만약 예외가 쓰레드에 의해 캐치되면, 쓰레드가 예외를 캐치했다는 것을
            확인할 방법이 필요합니다.
    
            주의 : 이 함수는 해당 인스턴스의 신원을 확인하기 위해
            해당 인스턴스를 호출하는 쓰레드에서 사용되는 함수입니다.
            """
            _async_raise( self._get_my_tid(), exctype )
    

    문서에 나와있는 바와 같이, 쓰레드가 파이썬 인터럽트 밖에서 busy 상태인 경우, 인터럽트를 캐치하지 못하기 때문에 이 방법이 완벽한 방법은 아닙니다.

    이 코드의 좋은 사용 패턴은 쓰레드가 특정한 예외를 캐치하여 정리 작업을 수행하도록 하는 것입니다. 이를 통해 작업을 중단시키고 정상적인 정리 과정을 거칠 수 있게끔 할 수 있죠.

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)