多线程Python项目疑似内存泄漏问题排查求助
内存异常增长大概率是Python代码逻辑缺陷和unix程序solve-field异常行为共同作用的结果,以下是具体分析和排查方向:
一、Python代码层面的潜在问题
线程池重复创建导致资源残留
在Controller.start()的while True循环内,每次迭代都会新建ThreadPoolExecutor实例。虽然with语句会自动关闭executor,但频繁创建销毁线程池可能导致线程资源未完全释放,长期运行后积累内存占用。subprocess输出未限制引发内存暴涨
AM.test_func()中使用capture_output=True会将solve-field的所有标准输出、错误输出全部读取到Python内存中。如果无效文件触发该程序输出巨量错误日志(比如GB级内容),会直接导致Python进程内存飙升至3.5G。方法调用参数错误引发无效异常
AM.start()方法定义需要fname参数,但Controller._start_func()中调用AM(f"{newFname}").start()时未传递该参数,会触发TypeError。虽然被try/except捕获返回1,但异常处理过程可能产生未清理的临时对象,长期积累增加内存占用。此外AM.__init__接收fname参数却未保存,属于冗余逻辑。multiprocessing队列的潜在积压风险
使用multiprocessing.Manager()创建的队列q,如果任务处理速度跟不上队列写入速度,会积压大量文件路径,间接增加内存占用(需结合实际任务量验证)。
二、unix程序solve-field的可能问题
错误场景下的巨量输出
当配置文件缺失或处理无效文件时,solve-field可能进入异常逻辑,产生海量错误输出。这些输出被Python通过capture_output=True读入内存后,直接导致进程内存暴涨。程序自身内存泄漏
如果solve-field在处理文件(尤其是无效文件)时存在内存泄漏,虽然子进程结束后系统会回收资源,但多次调用后可能导致系统级内存占用上升;若程序在错误场景下临时申请大量内存未释放,也会间接推高Python进程的内存统计。
三、排查与修复步骤
验证subprocess输出的影响
临时修改AM.test_func(),丢弃所有输出:p = subprocess.run(c, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)观察错误场景下内存是否还会飙升。若内存恢复正常,说明是输出内容过多导致的问题。
复用线程池减少资源消耗
将ThreadPoolExecutor初始化移到循环外部,避免频繁创建销毁:def start(self): event_handler = Handler() observer = watchdog.observers.Observer() observer.schedule(event_handler, path=cfg.paths["ipath"], recursive=True) observer.start() try: with concurrent.futures.ThreadPoolExecutor(max_workers=cfg.workers) as executor: while True: time.sleep(1) futures = {} while not q.empty(): newFname = q.get() futures_to_work = executor.submit(self._start_func, newFname) futures[futures_to_work] = newFname for future in concurrent.futures.as_completed(futures): name = futures.pop(future) print(f"{name} completed") except KeyboardInterrupt: observer.stop() observer.join()长期运行后观察内存是否仍持续增长至200-300M。
修复参数传递错误
修正AM类的参数逻辑:class AM(): def __init__(self, fname): self.fname = fname def test_func(self): c = f"solve-field {self.fname}".split(" ") p = subprocess.run(c, capture_output=True, text=True) out, err = p.stdout, p.stderr if out: print(out) if err: print(err) return 1 return 0 def start(self): res = self.test_func() return res避免不必要的异常处理开销。
排查
solve-field自身问题
直接在命令行手动运行solve-field处理无效文件或模拟配置缺失场景,用top或ps观察该进程的内存占用。如果进程在错误场景下内存占用极高,说明程序自身存在问题,需查看astrometry.net相关文档或提交issue。
内容的提问来源于stack exchange,提问作者jlipinski

