[python] 스레드를 죽일 방법이 있습니까?

플래그 / 세마포어 등을 설정 / 확인하지 않고 실행중인 스레드를 종료 할 수 있습니까?



답변

파이썬과 다른 언어로 쓰레드를 갑자기 죽이는 것은 일반적으로 나쁜 패턴입니다. 다음과 같은 경우를 생각해보십시오.

  • 스레드가 올바르게 닫아야하는 중요한 자원을 보유하고 있습니다.
  • 스레드가 종료해야하는 몇 가지 다른 스레드를 작성했습니다.

여유를 가질 수있는 경우 (자신의 스레드를 관리하는 경우)이를 처리하는 좋은 방법은 각 스레드가 규칙적인 간격으로 검사하여 종료 시간인지 확인하는 exit_request 플래그를 갖는 것입니다.

예를 들면 다음과 같습니다.

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,  *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        self._stop_event = threading.Event()

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()

이 코드에서는 stop()스레드를 종료하려고 할 때 스레드를 호출 하고을 사용하여 스레드가 올바르게 종료 될 때까지 기다려야합니다 join(). 스레드는 정기적으로 정지 플래그를 확인해야합니다.

그러나 실제로 스레드를 죽여야 할 경우가 있습니다. 예를 들어 통화량이 많은 외부 라이브러리를 래핑하고 중단하려는 경우가 있습니다.

다음 코드는 파이썬 스레드에서 예외를 발생시킬 수 있습니다.

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(ctypes.c_long(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(ctypes.c_long(tid), None)
        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 )

( Tomer Filiba의 Killable Threads 를 기반으로합니다 .의 반환 값에 대한 인용문은 이전 버전의 PythonPyThreadState_SetAsyncExc 에서 나온 것으로 보입니다 .)

문서에서 언급했듯이 스레드가 Python 인터프리터 외부에서 사용 중이면 중단을 포착하지 않기 때문에 이것은 마법의 총알이 아닙니다.

이 코드의 좋은 사용 패턴은 스레드가 특정 예외를 포착하고 정리를 수행하도록하는 것입니다. 이렇게하면 작업을 중단하고 여전히 적절한 정리를 수행 할 수 있습니다.


답변

그렇게 할 공식 API는 없습니다.

pthread_kill 또는 TerminateThread와 같이 스레드를 종료하려면 플랫폼 API를 사용해야합니다. pythonwin 또는 ctypes를 통해 이러한 API에 액세스 할 수 있습니다.

이것은 본질적으로 안전하지 않습니다. 가비지가되는 스택 프레임의 로컬 변수에서 수집 할 수없는 가비지가 발생할 수 있으며, 죽이는 스레드가 죽일 때 GIL이있는 경우 교착 상태가 발생할 수 있습니다.


답변

multiprocessing.Processp.terminate()

스레드를 죽이고 싶지만 플래그 / 잠금 / 신호 / 세마포어 / 이벤트 / 무엇이든 사용하지 않으려는 경우 스레드를 완전히 처리 된 프로세스로 승격시킵니다. 몇 개의 스레드 만 사용하는 코드의 경우 오버 헤드가 그렇게 나쁘지 않습니다.

예를 들어 이것은 차단 I / O를 실행하는 도우미 “스레드”를 쉽게 종료하는 데 편리합니다.

변환은 간단하다 : 관련 코드를 모두 교체에서 threading.Threadmultiprocessing.Process모든 queue.Queue과를 multiprocessing.Queue하고 필요한 통화를 추가p.terminate() 자식을 죽이고 싶어 당신의 부모 프로세스에p

에 대한 Python 설명서를multiprocessing 참조하십시오 .


답변

전체 프로그램을 종료하려는 경우 스레드를 “데몬”으로 설정할 수 있습니다. Thread.daemon을 참조하십시오
.


답변

다른 사람들이 언급했듯이, 표준은 정지 플래그를 설정하는 것입니다. 가벼운 것 (Thread의 서브 클래스 없음, 전역 변수 없음)의 경우 람다 콜백이 옵션입니다. (의 괄호에 유의하십시오 if stop().)

import threading
import time

def do_work(id, stop):
    print("I am thread", id)
    while True:
        print("I am thread {} doing something".format(id))
        if stop():
            print("  Exiting loop.")
            break
    print("Thread {}, signing off".format(id))


def main():
    stop_threads = False
    workers = []
    for id in range(0,3):
        tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads))
        workers.append(tmp)
        tmp.start()
    time.sleep(3)
    print('main: done sleeping; time to stop the threads.')
    stop_threads = True
    for worker in workers:
        worker.join()
    print('Finis.')

if __name__ == '__main__':
    main()

항상 플러시 print()되는 pr()기능으로 교체 (sys.stdout.flush() )으로 하면 쉘 출력의 정밀도가 향상 될 수 있습니다.

(Windows / Eclipse / Python3.3에서만 테스트 됨)


답변

이것은 thread2-killable thread (Python recipe)를 기반으로합니다.

ctypes를 통해서만 사용할 수있는 PyThreadState_SetasyncExc ()를 호출해야합니다.

이것은 Python 2.7.3에서만 테스트되었지만 다른 최신 2.x 릴리스에서 작동 할 수 있습니다.

import ctypes

def terminate_thread(thread):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(SystemExit)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(thread.ident), exc)
    if res == 0:
        raise ValueError("nonexistent 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(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


답변

협력하지 않고 스레드를 강제로 종료해서는 안됩니다.

스레드를 종료하면 시도 / 마지막으로 설정된 블록을 차단하여 잠금을 잠 그거나 파일을 열어 두는 등의 보장이 제거됩니다.

스레드를 강제로 종료하는 것이 좋은 아이디어라고 주장 할 수있는 유일한 시간은 프로그램을 빠르게 종료하지만 단일 스레드는 사용하지 않는 것입니다.