Flask与Watchdog:调试模式下多线程启动致事件重复问题
这个问题我之前帮朋友排查过类似的,核心就是Werkzeug调试重载器的进程模型和Watchdog线程的冲突——调试模式下,Werkzeug会先启动一个监控父进程,再fork出实际运行应用的子进程;如果你的Watchdog在父进程里也启动了,那父进程会一直驻留,对应的线程就会一直跑,和子进程里的新线程重复监听事件。咱们用几个方法就能搞定:
方案1:通过环境变量判断,只在应用子进程启动Watchdog
Werkzeug在启动应用子进程时,会自动设置WERKZEUG_RUN_MAIN环境变量为true,而监控父进程里这个变量不存在。我们可以利用这一点,只在子进程中启动Watchdog:
import os from flask import Flask from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler # 自定义你的文件事件处理器 class FileChangeHandler(FileSystemEventHandler): def on_modified(self, event): if not event.is_directory: print(f"[LOG] 文件更新:{event.src_path}") # 全局保存Observer实例,方便后续停止 watchdog_observer = None def start_watchdog_monitor(): global watchdog_observer if watchdog_observer is not None: return # 避免重复启动 handler = FileChangeHandler() watchdog_observer = Observer() watchdog_observer.schedule(handler, path="./monitor_dir", recursive=True) watchdog_observer.start() print("[INFO] Watchdog 监控已启动") def stop_watchdog_monitor(): global watchdog_observer if watchdog_observer is not None: watchdog_observer.stop() watchdog_observer.join() # 等待线程优雅退出 watchdog_observer = None print("[INFO] Watchdog 监控已停止") app = Flask(__name__) # 应用上下文销毁时,自动停止Watchdog @app.teardown_appcontext def cleanup_watchdog(exception): stop_watchdog_monitor() if __name__ == '__main__': # 只有当是Werkzeug的应用子进程时,才启动Watchdog if os.environ.get("WERKZEUG_RUN_MAIN") == "true": start_watchdog_monitor() app.run(debug=True)
这个方法的核心是:监控父进程不会启动Watchdog,只有实际处理请求的子进程才会启动;当代码修改触发重载时,旧的子进程会被杀死,对应的Watchdog线程也会随进程终止,新的子进程启动新的Watchdog,完全不会有残留。
方案2:将Watchdog线程设为守护线程
如果觉得环境变量判断有点麻烦,也可以把Watchdog线程设置为守护线程——守护线程会随着主进程的退出自动终止,不会留在后台运行。只需要在启动Observer时加上daemon=True:
def start_watchdog_monitor(): global watchdog_observer if watchdog_observer is not None: return handler = FileChangeHandler() watchdog_observer = Observer(daemon=True) # 设置为守护线程 watchdog_observer.schedule(handler, path="./monitor_dir", recursive=True) watchdog_observer.start() print("[INFO] Watchdog 监控已启动")
不过这个方法有个小缺点:如果主进程突然退出,守护线程可能来不及完成当前的事件处理(比如日志写入),但对于调试场景来说,这个影响几乎可以忽略。
为什么生产环境没问题?
你提到生产环境不会出现这个问题,原因很简单:生产环境不会启用Werkzeug的调试重载器,只会启动单一的应用进程,Watchdog也只会启动一次,自然不会有重复监听的情况。
这样调整后,调试时就不会再出现重复的日志记录了,要是你还有其他细节需要适配(比如监控路径动态调整),可以再细化逻辑~
内容的提问来源于stack exchange,提问作者fluffy

