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

如何为Flask的/build-task端点添加互斥锁,避免并发调用?

嘿,这个需求太典型了——长时间运行的API端点最怕重复触发,轻则浪费资源,重则导致数据混乱。我给你几个不同场景下的解决方案,你可以按需选择:

方案1:单进程Flask应用用全局变量锁

如果你的Flask是单进程运行(比如直接用flask run测试,或者生产环境单worker),最简单的方式就是用一个全局变量来标记任务是否在运行:

from flask import Flask, jsonify

app = Flask(__name__)
# 全局锁标记,初始为False表示任务未运行
is_build_running = False

@app.route('/build-task', methods=['POST'])
def build_task():
    global is_build_running
    if is_build_running:
        return jsonify({"error": "Build task is already running"}), 409
    
    try:
        is_build_running = True
        # 模拟30分钟的任务
        # time.sleep(1800)
        print("Starting long build task...")
        # 这里写你的实际任务逻辑
        return jsonify({"message": "Build task completed successfully"})
    finally:
        # 不管任务成功失败,都要重置锁
        is_build_running = False

if __name__ == '__main__':
    app.run()

⚠️ 注意:这个方案只适用于单进程场景,如果用了多worker(比如Gunicorn开多个进程),全局变量是进程隔离的,每个worker都有自己的is_build_running,锁就失效了。

方案2:多进程/分布式场景用外部存储锁

如果是生产环境多进程或者分布式部署,就得用外部共享存储来存锁状态,比如Redis或者数据库。这里举Redis的例子,因为它的SETNX(Set if Not Exists)命令天生适合做分布式锁:

首先安装Redis依赖:

pip install redis

然后修改代码:

from flask import Flask, jsonify
import redis
import time

app = Flask(__name__)
# 连接Redis,根据你的配置修改
r = redis.Redis(host='localhost', port=6379, db=0)
# 锁的key,自定义一个唯一标识
LOCK_KEY = "build_task_running"
# 锁的过期时间,设置比任务最长运行时间长一点,比如35分钟=2100秒
LOCK_EXPIRE = 2100

@app.route('/build-task', methods=['POST'])
def build_task():
    # 尝试获取锁:如果key不存在就设置,返回True;否则返回False
    acquired = r.set(LOCK_KEY, "1", ex=LOCK_EXPIRE, nx=True)
    if not acquired:
        return jsonify({"error": "Build task is already running"}), 409
    
    try:
        print("Starting long build task...")
        # 模拟30分钟任务
        # time.sleep(1800)
        # 你的实际任务逻辑
        return jsonify({"message": "Build task completed successfully"})
    finally:
        # 任务结束后主动释放锁
        r.delete(LOCK_KEY)

if __name__ == '__main__':
    app.run()

这个方案的好处是不管多少个worker或者多少台服务器,都能共享锁状态,而且设置了过期时间,就算任务意外崩溃(比如服务器断电),锁也会自动过期,不会一直卡死。

方案3:用Flask-Caching简化锁逻辑

如果你已经在用Flask-Caching扩展,也可以用它来实现锁,代码更简洁:

先安装:

pip install flask-caching

代码示例:

from flask import Flask, jsonify
from flask_caching import Cache
import time

app = Flask(__name__)
cache_config = {
    "CACHE_TYPE": "RedisCache",
    "CACHE_REDIS_HOST": "localhost",
    "CACHE_REDIS_PORT": 6379,
    "CACHE_REDIS_DB": 0
}
cache = Cache(app, config=cache_config)
LOCK_KEY = "build_task_running"
LOCK_EXPIRE = 2100

@app.route('/build-task', methods=['POST'])
def build_task():
    # 尝试获取缓存,不存在就设置并返回True
    if cache.get(LOCK_KEY):
        return jsonify({"error": "Build task is already running"}), 409
    
    try:
        cache.set(LOCK_KEY, "1", timeout=LOCK_EXPIRE)
        print("Starting long build task...")
        # time.sleep(1800)
        # 任务逻辑
        return jsonify({"message": "Build task completed successfully"})
    finally:
        cache.delete(LOCK_KEY)

if __name__ == '__main__':
    app.run()

额外提醒

  • 一定要给锁设置合理的过期时间,比你的任务最长可能运行时间长一点,避免死锁。
  • 任务执行过程中如果有异常,一定要在finally块里释放锁(或者依赖过期时间兜底)。
  • 如果是非常关键的任务,还可以考虑给锁加唯一标识(比如用UUID),防止误释放其他进程的锁(不过一般简单场景用上面的方法足够了)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:11:00