You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gunicorn多Worker启动Flask时delete脚本重复执行,需仅单次运行

解决Gunicorn多Worker模式下脚本仅执行一次的问题

问题原因

用gunicorn -w 4 -b 127.0.0.1:4004 test:app启动服务时,Gunicorn会创建4个独立的Worker子进程。你的代码中全局调用了manager()函数,每个Worker进程启动时都会执行这个函数,进而触发delete脚本运行,最终导致脚本被重复执行4次。

解决方案

方案1:使用Gunicorn的--preload参数

这是最简洁的解决方式,让Gunicorn在主进程中预先加载Flask应用,再fork出Worker进程。这样全局初始化代码只会在主进程执行一次,所有Worker共享已初始化的应用状态。

修改启动命令为:

gunicorn --preload -w 4 -b 127.0.0.1:4004 test:app

代码无需额外修改,原有的manager()全局调用会在主进程仅执行一次,Worker进程是fork生成的,不会重复执行全局初始化逻辑。

方案2:用文件锁确保单进程执行脚本

如果无法使用--preload(比如存在进程不安全的初始化操作),可以通过文件锁机制,保证只有第一个启动的Worker进程执行脚本:

修改后的完整代码如下:

import multiprocessing
import subprocess
import os
import sys
from flask import Flask, request
from flask_restful import Resource, Api
from module.delete import run
import logging
import fcntl

logging.basicConfig(
    format='[%(levelname)s][%(asctime)s][PID %(process)d]: (%(filename)s) %(message)s', level=logging.INFO)

app = Flask(__name__)
api = Api(app)

ports = {
    'prod': 4004,
    'ppr': 4004,
    'dev': 4004
}

def run_delete_script():
    # 创建文件锁,确保只有一个进程执行脚本
    lock_file = '/tmp/delete_script.lock'
    with open(lock_file, 'w') as f:
        try:
            # 获取排他锁,非阻塞模式
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
            config = run()
            config.run()
            logging.info("Delete脚本执行完成")
        except BlockingIOError:
            # 其他进程已持有锁,跳过执行
            logging.info("Delete脚本已被其他进程执行,当前进程跳过")

# 在应用初始化阶段执行一次
run_delete_script()

class Test(Resource):
    logging.info(f"Resource = {Resource}")
    def post(self):
        return {'message': 'Payload stored successfully'}, 200

api.add_resource(Test, '/testing')

if __name__ == '__main__':
    env = 'master' if len(sys.argv) == 1 else sys.argv[1]
    app.run(port=ports.get(env, 4004))

方案3:判断Gunicorn主进程执行脚本

通过检查进程关系,只让Gunicorn的主进程执行脚本:

def run_delete_script():
    # 仅当当前进程是Gunicorn主进程时执行
    if 'gunicorn' in os.path.basename(sys.argv[0]):
        # Worker进程的父进程是Gunicorn主进程,判断当前PID是否等于父PID
        if os.getpid() != os.getppid():
            return
    config = run()
    config.run()

run_delete_script()

注意事项

  • 使用--preload时,避免在全局初始化中创建进程不安全的资源(如数据库连接、Socket连接),这些资源会被Worker进程继承,可能导致连接异常。
  • 文件锁方案需要确保锁文件所在目录有写入权限,服务重启时锁会自动释放,无需手动清理。

内容的提问来源于stack exchange,提问作者titirsha tsi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 09:57:29