Flask结合Matplotlib报错RuntimeError:主线程不在主循环中
Flask路由线程问题与Matplotlib主线程绘图解决方案
首先直接回答你的第一个问题:是的,Flask的默认服务器(Werkzeug)在默认配置下是多线程运行的——每个进来的请求都会被分配到一个独立的线程中处理,所以你的sendOutput路由函数确实是在非主线程里执行的。这也是你遇到Matplotlib绘图保存问题的核心原因之一,因为Matplotlib的大多数后端(比如默认的TkAgg、Agg)都不是线程安全的,在子线程里调用fig.savefig很容易触发异常或渲染错误。
接下来给你两种实用方案,帮你让fig.savefig运行在主线程:
方案1:使用线程安全队列实现任务调度
这是最可靠的轻量方案,思路是让路由线程只负责生成绘图所需的数据与配置,把实际的保存任务放到线程安全队列中,由主线程专门处理队列里的保存请求。
步骤与示例代码:
- 导入Python标准库的
queue和threading模块,创建全局的线程安全队列 - 在主线程启动一个后台线程,持续监听队列,一旦收到任务就执行
fig.savefig - 路由函数中仅准备绘图数据,将保存任务(比如fig对象、保存路径)放入队列
from flask import Flask import queue import threading import matplotlib.pyplot as plt app = Flask(__name__) # 创建线程安全的保存任务队列 save_queue = queue.Queue() def fig_save_worker(): """主线程后台运行的任务处理函数,专门负责图片保存""" while True: fig, save_path = save_queue.get() try: fig.savefig(save_path) plt.close(fig) # 及时释放资源,避免内存泄漏 except Exception as e: print(f"图片保存失败: {str(e)}") finally: save_queue.task_done() # 在启动Flask前启动后台任务线程(daemon=True表示随主线程退出而终止) threading.Thread(target=fig_save_worker, daemon=True).start() @app.route('/open', methods=['GET', 'POST']) def sendOutput(): global loss,a2,xP,yP,scale,sess,fig test_X,test_Y = own_model.getEvaluateData(scale) cost,ans = sess.run([loss,a2],feed_dict={xP:test_X,yP:test_Y}) # 假设你已经完成了fig的绘图逻辑 # 将保存任务放入队列,由主线程的worker处理 save_queue.put((fig, './output_result.png')) # 返回请求响应 return {"cost": cost, "status": "图片保存任务已提交"} if __name__ == '__main__': app.run() # 默认threaded=True,可省略
方案2:切换Matplotlib为无GUI后端(应急用,不推荐复杂场景)
如果你不想调整代码架构,可以尝试强制Matplotlib使用非GUI的后端(比如Agg),并确保整个绘图流程(创建fig、绘图、保存)在同一个线程中完成。但注意:即使切换后端,Matplotlib的状态机(当前figure、axes等)依然不是线程安全的,多请求并发时仍可能出问题。
在代码最开头添加:
import matplotlib matplotlib.use('Agg') # 切换到无GUI后端 import matplotlib.pyplot as plt
另外补充:TensorFlow的Session对象本身是线程安全的(只要不同时在同一个会话中调用sess.run),所以你在路由线程中调用sess.run是没问题的。如果后续有更复杂的多线程操作,也可以考虑把会话封装到线程本地存储中,避免全局变量冲突。
内容的提问来源于stack exchange,提问作者Mihir Prabhudesai
相关产品推荐
相关产品推荐

