翻译自 https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/
通常,突然终止线程被认为是一种糟糕的编程习惯。突然终止线程可能会使必须正确关闭的关键资源处于打开状态。但是您可能希望在某个特定时间段过去或产生某个中断后终止线程。
下面有6种方式杀死线程,其中前两种较常用,我在原文的基础上进行了一些修改,以覆盖更多的情况。
在 python 线程中抛出异常
使用 flag / Event()
使用 traces 杀死线程
使用 multiprocessing module 杀死线程
通过将其设置为守护进程来杀死 Python 线程
使用隐藏函数 _stop()
0x01 在 python 线程中抛出异常 此方法使用函数 PyThreadState_SetAsyncExc() 在线程中引发异常。
这种方法主要分两步。第一步获取线程id,第二步调用 SetAsyncExc,另外可以通过返回值查看是否杀死成功。 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # Python program raising # exceptions in a python # thread import threading import ctypes import time class thread_with_exception(threading.Thread): def __init__(self, name): threading.Thread.__init__(self) self.name = name def run(self): # target function of the thread class try: while True: print('running ' + self.name) finally: print('ended') def get_id(self): # returns id of the respective thread if hasattr(self, '_thread_id'): return self._thread_id for id, thread in threading._active.items(): if thread is self: return id def raise_exception(self): thread_id = self.get_id() 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') t1 = thread_with_exception('Thread 1') t1.start() time.sleep(2) t1.raise_exception() t1.join()
另外,这里 还有另一种方法获取线程的 id:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import threading import time import inspect import ctypes 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(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread): _async_raise(thread.ident, SystemExit) class TestThread(threading.Thread): def run(self): print("begin run the child thread") while True: print("sleep 1s") time.sleep(1) if __name__ == "__main__": print("begin run main thread") t = TestThread() t.start() time.sleep(3) stop_thread(t) print("main thread end")
当我们在一台机器上运行上面的代码时,你会注意到,只要函数 raise_exception() 被调用,目标函数 run() 就会结束。这是因为一旦引发异常,程序控制就会跳出 try 块,run() 函数将终止。之后可以调用 join() 函数来终止线程。在没有函数 run_exception() 的情况下,目标函数 run() 将一直运行,并且永远不会调用 join() 函数来终止线程。
0x02 使用 flag 为了杀死一个线程,我们可以声明一个停止标志,这个标志会被线程检查。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # Python program showing # how to kill threads # using set/reset stop # flag import threading import time def run(): while True: print('thread running') global stop_threads if stop_threads: break stop_threads = False t1 = threading.Thread(target = run) t1.start() time.sleep(1) stop_threads = True t1.join() print('thread killed')
在上面的代码中,一旦设置了全局变量 stop_threads,目标函数 run() 就会结束,并且可以使用 t1.join() 杀死线程 t1。但是由于某些原因,人们可能会避免使用全局变量。对于这些情况,可以传递函数对象以提供类似的功能,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # Python program killing # threads using stop # flag import threading import time def run(stop): while True: print('thread running') if stop(): break def main(): stop_threads = False t1 = threading.Thread(target = run, args =(lambda : stop_threads, )) t1.start() time.sleep(1) stop_threads = True t1.join() print('thread killed') main()
上面代码中传入的函数对象总是返回局部变量 stop_threads 的值。这个值在函数 run() 中被检查,一旦 stop_threads 被重置,run() 函数结束并且线程可以被杀死。
另外,使用 threading.Event()
可以更优雅的实现这一功能。
0x03 使用 traces 杀死线程 此方法通过在每个线程中使用 traces 来工作。每个 trace 都会在检测到某些刺激或标志时自行终止,从而立即终止关联的线程。例如
Python program using 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 # traces to kill threads import sys import trace import threading import time class thread_with_trace(threading.Thread): def __init__(self, *args, **keywords): threading.Thread.__init__(self, *args, **keywords) self.killed = False def start(self): self.__run_backup = self.run self.run = self.__run threading.Thread.start(self) def __run(self): sys.settrace(self.globaltrace) self.__run_backup() self.run = self.__run_backup def globaltrace(self, frame, event, arg): if event == 'call': return self.localtrace else: return None def localtrace(self, frame, event, arg): if self.killed: if event == 'line': raise SystemExit() return self.localtrace def kill(self): self.killed = True def func(): while True: print('thread running') t1 = thread_with_trace(target = func) t1.start() time.sleep(2) t1.kill() t1.join() if not t1.isAlive(): print('thread killed')
在这段代码中,start() 被稍微修改为使用 settrace() 设置系统跟踪功能。本地跟踪函数的定义是,无论何时设置相应线程的终止标志(已终止),都会引发 SystemExit 异常执行下一行代码,结束目标函数func的执行。现在可以使用 join() 终止线程。
0x04 使用 multiprocessing module 杀死线程 Python 的multiprocessing module 允许您以类似于使用线程模块生成线程的方式生成进程。multiprocessing module 的接口与 threading 的接口类似。例如,在给定的代码中,我们创建了三个线程(进程),它们从 1 计数到 9。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # Python program creating # three threads import threading import time # counts from 1 to 9 def func(number): for i in range(1, 10): time.sleep(0.01) print('Thread ' + str(number) + ': prints ' + str(number*i)) # creates 3 threads for i in range(0, 3): thread = threading.Thread(target=func, args=(i,)) thread.start()
上述代码的功能也可以通过类似的方式使用多处理模块来实现,只需很少的改动。请参阅下面给出的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Python program creating # thread using multiprocessing # module import multiprocessing import time def func(number): for i in range(1, 10): time.sleep(0.01) print('Processing ' + str(number) + ': prints ' + str(number*i)) for i in range(0, 3): process = multiprocessing.Process(target=func, args=(i,)) process.start()
尽管这两个模块的接口相似,但是这两个模块的实现却截然不同。所有线程共享全局变量,而进程彼此完全分离。因此,与杀死线程相比,杀死进程要安全得多。 Process 类提供了一种方法 terminate() 来终止进程。现在,回到最初的问题。假设在上面的代码中,我们想要在 0.03s 过去后杀死所有进程。此功能是使用以下代码中的 multiprocessing 实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # Python program killing # a thread using multiprocessing # module import multiprocessing import time def func(number): for i in range(1, 10): time.sleep(0.01) print('Processing ' + str(number) + ': prints ' + str(number*i)) # list of all processes, so that they can be killed afterwards all_processes = [] for i in range(0, 3): process = multiprocessing.Process(target=func, args=(i,)) process.start() all_processes.append(process) # kill all processes after 0.03s time.sleep(0.03) for process in all_processes: process.terminate()
虽然这两个模块有不同的实现。上面代码中多处理模块提供的这个功能类似于杀死线程。因此,只要我们需要在 Python 中实现线程终止,multiprocessing
就可以作为一个简单的替代方案。
0x05 通过将其设置为守护进程(daemon)来杀死 Python 线程 守护线程 是那些在主程序退出时被杀死的线程。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 import threading import time import sys def func(): while True: time.sleep(0.5) print("Thread alive, and it won't die on program termination") t1 = threading.Thread(target=func) t1.start() time.sleep(2) sys.exit()
请注意,线程 t1 保持活动状态并阻止主程序通过 sys.exit() 退出。在 Python 中,任何活动的非守护线程都会阻止主程序退出。然而,一旦主程序退出,守护线程本身就会被杀死。换句话说,主程序一退出,所有的守护线程就被杀死了。要将线程声明为守护进程,我们将关键字参数 daemon 设置为 True。例如,在给定的代码中,它演示了守护线程的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Python program killing # thread using daemon import threading import time import sys def func(): while True: time.sleep(0.5) print('Thread alive, but it will die on program termination') t1 = threading.Thread(target=func) t1.daemon = True t1.start() time.sleep(2) sys.exit()
请注意,一旦主程序退出,线程 t1 就会被终止。在程序终止可用于触发线程终止的情况下,此方法被证明非常有用。请注意,在 Python 中,只要所有非守护线程都死了,主程序就会终止,而不管有多少守护线程处于活动状态。因此,这些守护线程所持有的资源,例如打开的文件、数据库事务等,可能无法正常释放。 python 程序中的初始控制线程不是守护线程。除非确定知道这样做不会导致任何泄漏或死锁,否则不建议强行终止线程。
0x06 使用隐藏函数 _stop()
为了杀死一个线程,我们使用隐藏函数 _stop()
这个函数没有记录但可能会在下一版本的 python 中消失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # Python program killing # a thread using ._stop() # function import time import threading class MyThread(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(MyThread, self).__init__(*args, **kwargs) self._stop = threading.Event() # function using _stop function def stop(self): self._stop.set() def stopped(self): return self._stop.isSet() def run(self): while True: if self.stopped(): return print("Hello, world!") time.sleep(1) t1 = MyThread() t1.start() time.sleep(5) t1.stop() t1.join()
注意: 以上方法在某些情况下可能不起作用,因为 python 没有提供任何直接杀死线程的方法。