플래그 / 세마포어 등을 설정 / 확인하지 않고 실행중인 스레드를 종료 할 수 있습니까?
답변
파이썬과 다른 언어로 쓰레드를 갑자기 죽이는 것은 일반적으로 나쁜 패턴입니다. 다음과 같은 경우를 생각해보십시오.
- 스레드가 올바르게 닫아야하는 중요한 자원을 보유하고 있습니다.
- 스레드가 종료해야하는 몇 가지 다른 스레드를 작성했습니다.
여유를 가질 수있는 경우 (자신의 스레드를 관리하는 경우)이를 처리하는 좋은 방법은 각 스레드가 규칙적인 간격으로 검사하여 종료 시간인지 확인하는 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.Process
캔p.terminate()
스레드를 죽이고 싶지만 플래그 / 잠금 / 신호 / 세마포어 / 이벤트 / 무엇이든 사용하지 않으려는 경우 스레드를 완전히 처리 된 프로세스로 승격시킵니다. 몇 개의 스레드 만 사용하는 코드의 경우 오버 헤드가 그렇게 나쁘지 않습니다.
예를 들어 이것은 차단 I / O를 실행하는 도우미 “스레드”를 쉽게 종료하는 데 편리합니다.
변환은 간단하다 : 관련 코드를 모두 교체에서 threading.Thread
와 multiprocessing.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")
답변
협력하지 않고 스레드를 강제로 종료해서는 안됩니다.
스레드를 종료하면 시도 / 마지막으로 설정된 블록을 차단하여 잠금을 잠 그거나 파일을 열어 두는 등의 보장이 제거됩니다.
스레드를 강제로 종료하는 것이 좋은 아이디어라고 주장 할 수있는 유일한 시간은 프로그램을 빠르게 종료하지만 단일 스레드는 사용하지 않는 것입니다.