之前在写一些业务逻辑的时候, 总会碰到一些很诡异的bug, 比如说, 我们的服务可能要同时做那么几件事情, 当然大家现在都流行微服务了, 会把不同的事件抽取成为服务, 可惜我们没有, 那么有时因为服务器网络故障, 所以你的客户端请求服务端的时候crash了, 而你可能整个流程都是在一个线程里面的, 且这个流程都是围绕着客户端和服务端来展开的, 所以之前采取的方法就是抽取出来一个方法, 在外面加上一个while - try - except - else - break, 但是这么写就有点不大好用, 而且传说python的while True是很占CPU的, 所以几番摸索下, 倒腾出了另外一种方式, 代码如下:
import threading import time class WThread(threading.Thread): # 打印出创建线程的名字, 用于调试 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print(self.name + " created") # 线程回收时打印信息, 便于确认线程已回收 def __del__(self): print("线程释放了..." + self.name) # 一个注定会crash的方法, 占用一个线程 def show(): for i in range(10): print("------------") _ = 1/(9-i) time.sleep(0.5) # 监测其他线程是否退出的方法, 如果线程退出, 则再开启一个线程, 并添加至列表中 def run(li): while True: for t in li: if t[1] is not None: if not t[0].is_alive(): sub_t = WThread(target=t[1]) sub_t.setDaemon(True) sub_t.start() li.remove(t) li.insert(0, (sub_t, t[1])) time.sleep(1) # 另一个正常线程 def say(): while True: print("------------") time.sleep(1) # 主函数, 创建三个线程, 一个用于监听, 一个正常, 还有一个异常 def main(): li = [] t = WThread(target=show) t.setDaemon(True) t.start() li.append((t, show)) t1 = WThread(target=say) t1.setDaemon(True) t1.start() li.append((t1, say)) t2 = WThread(target=run, args=(li, )) t2.start() li.append((t2, None)) t2.join() if __name__ == '__main__': main()总结代码的意思是, 单独开启一个线程去监听其他线程是否crash, 如果crash的话, 就直接新建一个线程, 然后把原来的线程从数据中移除, 把新建的线程添加到列表中。
经过测试, 发现上面的代码创建的线程资源都能够正常回收, 不会存在强引用的情况(其他资源强引用的情况需要自己监测), 但是还有个问题, 在遍历中, 是不推荐对列表进行修改的, 会影响列表的效果, 但是一般在这里影响不是太大...
总结: 最好不要自己造轮子, 网上有很多更优方案, 我这只是为了应对前期需求叠加的产物, 实际开发中, 尽量做到先调研清楚需求, 然后想好架子怎么搭, 然后再动手, 是在碰到需求不断叠加的情况, 也可以做服务拆分, 真的到了我这种地步, 请谨慎处理, 做好测试, 不然等待你的就是修不完的BUG...
更新:
基于以上考虑更新了一下代码:
import threading import time class WThread(threading.Thread): # 打印出创建线程的名字, 用于调试 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print(self.name + " created") # 线程回收时打印信息, 便于确认线程已回收 def __del__(self): print("线程释放了..." + self.name) # 一个注定会crash的方法, 占用一个线程 def show(): for i in range(10): print("------------") _ = 1/(9-i) time.sleep(0.5) # 监测其他线程是否退出的方法, 如果线程退出, 则再开启一个线程, 并添加至列表中 def run(li): while True: append_li = [] rm_li = [] for t in li: if t[1] is not None: if not t[0].is_alive(): sub_t = WThread(target=t[1]) sub_t.setDaemon(True) sub_t.start() rm_li.append(t) append_li.append((sub_t, t[1])) # 不修改外部引用, 不在遍历时修改列表 for d_item in rm_li: if d_item in li: li.remove(d_item) for a_item in append_li: li.append(a_item) time.sleep(1) # 另一个正常线程 def say(): while True: print("------------") time.sleep(1) # 主函数, 创建三个线程, 一个用于监听, 一个正常, 还有一个异常 def main(): li = [] t = WThread(target=show) t.setDaemon(True) t.start() li.append((t, show)) t1 = WThread(target=say) t1.setDaemon(True) t1.start() li.append((t1, say)) t2 = WThread(target=run, args=(li, )) t2.start() li.append((t2, None)) t2.join() if __name__ == '__main__': main()里面修改了run()方法, 之所以那么修改,是基于个人的一点小习惯, 尽量在引用传参时不改变引用变量, 这里比较特殊, 实际是一个死循环, 但是考虑到平时的习惯, 所以还是这么写了, 实际上完全可以考虑另外创建一个数组把原来的li替换掉, 但是这样li内部的情形不会反馈到引用上面, 对于其他地方想使用li的情况来说, 无法做到完全同步