是否可能在不设置/检查任何标志/信号量等的情况下终止正在运行的线程?
是否可能在不设置/检查任何标志/信号量等的情况下终止正在运行的线程?
Thread.stop
方法,这是绝对可行的:import sys
import threading
import time
class StopThread(StopIteration):
pass
threading.SystemExit = SystemExit, StopThread
class Thread2(threading.Thread):
def stop(self):
self.__stop = True
def _bootstrap(self):
if threading._trace_hook is not None:
raise ValueError('Cannot run thread with tracing!')
self.__stop = False
sys.settrace(self.__trace)
super()._bootstrap()
def __trace(self, frame, event, arg):
if self.__stop:
raise StopThread()
return self.__trace
class Thread3(threading.Thread):
def _bootstrap(self, stop_thread=False):
def stop():
nonlocal stop_thread
stop_thread = True
self.stop = stop
def tracer(*_):
if stop_thread:
raise StopThread()
return tracer
sys.settrace(tracer)
super()._bootstrap()
###############################################################################
def main():
test1 = Thread2(target=printer)
test1.start()
time.sleep(1)
test1.stop()
test1.join()
test2 = Thread2(target=speed_test)
test2.start()
time.sleep(1)
test2.stop()
test2.join()
test3 = Thread3(target=speed_test)
test3.start()
time.sleep(1)
test3.stop()
test3.join()
def printer():
while True:
print(time.time() % 1)
time.sleep(0.1)
def speed_test(count=0):
try:
while True:
count += 1
except StopThread:
print('Count =', count)
if __name__ == '__main__':
main()
Thread3
类似乎比Thread2
类运行代码快大约33%。
如果对Python的C API有足够的了解,并且使用ctypes
模块,就可以以更高效的方式在需要时停止线程。使用sys.settrace
的问题在于跟踪函数在每条指令之后运行。如果在需要中止的线程上引发异步异常,就不会产生执行速度的惩罚。以下代码在这方面提供了一些灵活性:
#! /usr/bin/env python3
import _thread
import ctypes as _ctypes
import threading as _threading
_PyThreadState_SetAsyncExc = _ctypes.pythonapi.PyThreadState_SetAsyncExc
# noinspection SpellCheckingInspection
_PyThreadState_SetAsyncExc.argtypes = _ctypes.c_ulong, _ctypes.py_object
_PyThreadState_SetAsyncExc.restype = _ctypes.c_int
# noinspection PyUnreachableCode
if __debug__:
# noinspection PyShadowingBuiltins
def _set_async_exc(id, exc):
if not isinstance(id, int):
raise TypeError(f'{id!r} not an int instance')
if not isinstance(exc, type):
raise TypeError(f'{exc!r} not a type instance')
if not issubclass(exc, BaseException):
raise SystemError(f'{exc!r} not a BaseException subclass')
return _PyThreadState_SetAsyncExc(id, exc)
else:
_set_async_exc = _PyThreadState_SetAsyncExc
# noinspection PyShadowingBuiltins
def set_async_exc(id, exc, *args):
if args:
class StateInfo(exc):
def __init__(self):
super().__init__(*args)
return _set_async_exc(id, StateInfo)
return _set_async_exc(id, exc)
def interrupt(ident=None):
if ident is None:
_thread.interrupt_main()
else:
set_async_exc(ident, KeyboardInterrupt)
# noinspection PyShadowingBuiltins
def exit(ident=None):
if ident is None:
_thread.exit()
else:
set_async_exc(ident, SystemExit)
class ThreadAbortException(SystemExit):
pass
class Thread(_threading.Thread):
def set_async_exc(self, exc, *args):
return set_async_exc(self.ident, exc, *args)
def interrupt(self):
self.set_async_exc(KeyboardInterrupt)
def exit(self):
self.set_async_exc(SystemExit)
def abort(self, *args):
self.set_async_exc(ThreadAbortException, *args)
self.__stop
。需要注意的是,就像这里的其他解决方案一样,它实际上不会打断阻塞调用,因为跟踪函数只有在进入新的局部作用域时才被调用。值得注意的是,sys.settrace
主要用于实现调试器、剖析等,因此被认为是 CPython 的一个实现细节,并不能保证在其他 Python 实现中存在。 - danoThread2
类的最大问题之一是其运行代码速度大约慢了十倍。可能仍有一些人认为这是可以接受的。 - Noctis Skytower最好不要强制终止线程。 一个方法是在线程的循环中引入“try”块,并在想要停止线程时抛出异常(例如,使用break/return/...停止for/while/...)。 我在我的应用程序中使用过这种方法,它很有效...
我知道现在已经晚了,但我一直在困扰一个类似的问题,下面的方法似乎能完美地解决我的问题,并且让我在子线程退出时进行一些基本的线程状态检查和清理:
import threading
import time
import atexit
def do_work():
i = 0
@atexit.register
def goodbye():
print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
(i, threading.currentThread().ident))
while True:
print i
i += 1
time.sleep(1)
t = threading.Thread(target=do_work)
t.daemon = True
t.start()
def after_timeout():
print "KILL MAIN THREAD: %s" % threading.currentThread().ident
raise SystemExit
threading.Timer(2, after_timeout).start()
产生:
0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]
SystemExit
只有两个特殊属性:它不会产生回溯(当任何线程通过抛出异常退出时),并且如果 主 线程通过抛出异常退出,它将设置退出状态(同时仍然等待其他非守护线程退出)。 - Davis HerringSystemExit
中断。必须通过终端使用 kill -9
终止脚本。 - A Kareem以下方法可用于终止一个线程:
kill_threads = False
def doSomething():
global kill_threads
while True:
if kill_threads:
thread.exit()
......
......
thread.start_new_thread(doSomething, ())
即使线程的代码是写在另一个模块中的,也可以使用此方法来终止线程。我们可以在该模块中声明一个全局变量,并使用它来终止在该模块中生成的线程。
我通常使用此方法在程序退出时终止所有线程。这可能不是终止线程的完美方式,但可以有所帮助。
这里有另一种实现方式,用极其简洁的代码来完成。它适用于2021年Python 3.7:
import ctypes
def kill_thread(thread):
"""
thread: a threading.Thread object
"""
thread_id = thread.ident
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
print('Exception raise failure')
翻译自这里:https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/
本文介绍了在Python中关闭线程的不同方法。
PyThreadState_SetAsyncExc
方法只是对线程退出进行“调度”。它不会杀死线程,特别是当它正在执行外部C库时。尝试使用该方法终止sleep(100)
。它将在100秒后被“终止”。它与while flag:
-> flag = False
方法一样有效。 - sausixfrom ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))
t 是您的 Thread
对象。
阅读 Python 源代码 (Modules/threadmodule.c
和 Python/thread_pthread.h
),您可以看到 Thread.ident
是一个 pthread_t
类型,因此您可以在 Python 中使用 libpthread
执行任何 pthread
可以执行的操作。
is_alive()
检查进程状态并使用“终止”来完成它们(还可以避免GIL问题)。但有时,在Windows中执行代码时可能会遇到更多问题。SystemExit
,您最终可以终止 Python 线程。import threading
import ctypes
def _async_raise(tid, excobj):
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(excobj))
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(tid, 0)
raise SystemError("PyThreadState_SetAsyncExc failed")
class Thread(threading.Thread):
def raise_exc(self, excobj):
assert self.isAlive(), "thread must be started"
for tid, tobj in threading._active.items():
if tobj is self:
_async_raise(tid, excobj)
return
# the thread was alive when we entered the loop, but was not found
# in the dict, hence it must have been already terminated. should we raise
# an exception here? silently ignore?
def terminate(self):
# must raise the SystemExit type, instead of a SystemExit() instance
# due to a bug in PyThreadState_SetAsyncExc
self.raise_exc(SystemExit)
为了进一步完善@SCB的想法(这正是我所需要的),创建一个KillableThread子类,并使用自定义函数:
from threading import Thread, Event
class KillableThread(Thread):
def __init__(self, sleep_interval=1, target=None, name=None, args=(), kwargs={}):
super().__init__(None, target, name, args, kwargs)
self._kill = Event()
self._interval = sleep_interval
print(self._target)
def run(self):
while True:
# Call custom function with arguments
self._target(*self._args)
# If no kill signal is set, sleep for the interval,
# If kill signal comes in while sleeping, immediately
# wake up and handle
is_killed = self._kill.wait(self._interval)
if is_killed:
break
print("Killing Thread")
def kill(self):
self._kill.set()
if __name__ == '__main__':
def print_msg(msg):
print(msg)
t = KillableThread(10, print_msg, args=("hello world"))
t.start()
time.sleep(6)
print("About to kill thread")
t.kill()
自然地,就像 @SBC 一样,线程不会等待运行新的循环来停止。在这个例子中,你会看到“Killing Thread”消息被打印,紧接着是“About to kill thread”,而不是等待4秒钟让线程完成(因为我们已经睡了6秒钟)。
KillableThread 构造函数的第二个参数是你的自定义函数(这里是 print_msg)。Args 参数是调用函数时将使用的参数(这里是“hello world”)。