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


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

  • 2016년 05월 31일에 작성됨

조회수 743


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 상태인 경우, 인터럽트를 캐치하지 못하기 때문에 이 방법이 완벽한 방법은 아닙니다.

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

  • 2016년 06월 01일에 작성됨

로그인이 필요한 기능입니다.

Hashcode는 개발자들을 위한 무료 QnA사이트 입니다. 작성한 답변에 다른 개발자들이 댓글을 작성하거나 좋아요/싫어요를 할 수 있기 때문에 계정을 필요로 합니다.
► 로그인
► 계정만들기
Close