一直以来如何停止python线程是件比较麻烦的事情, 因为很多操作需要放到线程中去做, 而有时候又需要即时关闭线程, 比如对于硬件串口的读取。
在之前停止线程有几种方式, 要么在内部的循环里加信号监听, 或者开子进程, 调用进程的停止方法去停止专门为了线程开的进程, 但是前者如果逻辑比较复杂, 循环中各部分可能都有不同的逻辑再跑, 可能信号监听会有错漏, 而后者, 进程本身就是一种资源消耗, 在开发板中, 多开几个进程, 也就资源耗尽了。
这次在网上搜罗了一番, 找到了ctypes里面的pythonapi中, 有个PyTrheadState_SetAsyncExc的东西, 貌似可以修改线程的状态, 尝试了下, 确实可是, 那么加以封装, 基本上就可以在原有threading.Thread上面封装出来一个带stop的线程类。
同时附加一个blinker的信号通知, 可以在子线程中触发调用主线程中方法, 这样可以更方便的使用异步方式解耦主子线程中的逻辑。
import ctypes
import inspect
import threading
import time
from blinker import signal
s = signal('king')
def animal(*args, **kwargs):
print(args)
if args[0] == 10:
print('exit')
t.stop()
def async_print():
for i in range(1000):
s.send(i)
time.sleep(1)
s.connect(animal)
class MyThread(threading.Thread):
def stop(self):
try:
self._async_raise(self.ident, SystemExit)
except Exception as e:
print(str(e))
def _async_raise(self, tid, exctype):
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise Exception('invalid thread id')
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
if __name__ == '__main__':
t = MyThread(target=async_print)
t.start()
t.join()