1 답변
-
일반적으로 갑자기 쓰레드를 죽이는 것은 파이썬에서나 다른 언어에서나 좋지 않은 패턴입니다. 아래와 같은 경우를 생각해보세요 :
* 쓰레드가 적절히 닫아야 할 중요한 리소스를 사용하고 있는 경우 * 쓰레드가 함께 죽여야 할 여러 쓰레드를 생성해놓은 경우
쓰레드를 관리하고 있을 때, 이를 해결할 좋은 접근법은 각각의 쓰레드가 일정한 시간 간격으로 스스로를 종료시켜야 하는지를 확인할 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 상태인 경우, 인터럽트를 캐치하지 못하기 때문에 이 방법이 완벽한 방법은 아닙니다.
이 코드의 좋은 사용 패턴은 쓰레드가 특정한 예외를 캐치하여 정리 작업을 수행하도록 하는 것입니다. 이를 통해 작업을 중단시키고 정상적인 정리 과정을 거칠 수 있게끔 할 수 있죠.
댓글 입력