Django使用threading启动循环任务仍阻塞HTTP请求的原因查询
问题描述
以下是价格监控模块的代码:
import threading import time import pandas as pd from binance.client import Client import json from api_create_alert.models import Alerts from service_get_data.get_api_secret import get_secret alerts = Alerts.objects.all().values() df = pd.DataFrame(alerts) data = get_secret() data = json.loads(data) client = Client(data['BINANCE_PUBLIC_API'], data['BINANCE_SECRET_API']) def price_watch(): interval = 10 while True: coins = client.get_all_tickers() for coin in coins: symbol = coin['symbol'] price = float(coin['price']) rows = df[((df.symbol == symbol) & (df.direction == 'ABOVE') & (price > df.price)) | ((df.symbol == symbol) & (df.direction == 'BELOW') & (price < df.price))] if len(rows) > 0: print(rows) time.sleep(interval) thread = threading.Thread(target=price_watch) thread.start()
我在apps.py中按如下方式导入该模块,在服务启动时运行任务:
def ready(self): if os.environ.get("RUN_MAIN") == "true": import service_price_watch.price_watch
该任务是每10秒执行一次的循环,但实际运行时每次循环启动,Django应用都会被阻塞,无法响应任何HTTP请求,直到循环执行完毕才恢复。我已经使用了threading模块启动任务,该模块的作用不就是实现异步操作吗?请问为什么还会出现阻塞请求的情况?
原因分析
- Python的线程受全局解释器锁(GIL)限制,同一时间只有一个线程能执行Python字节码。你代码中每次循环要遍历币安返回的上千个交易对,还要执行pandas全局筛选,属于CPU密集型操作,会长时间占用GIL,导致处理HTTP请求的主线程拿不到执行权限,出现阻塞。
- Django默认的开发服务器(runserver)默认是单线程运行模式,所有请求和后台线程共享同一个进程的CPU资源,进一步放大了阻塞效应。
- 你创建的线程没有设置为守护线程,虽然不是直接导致阻塞的原因,但会出现Django进程退出后子线程仍残留的问题。
- 额外的隐藏问题:你的告警规则DataFrame是在模块导入时一次性查询生成的,后续用户新增、修改、删除告警规则都不会同步到内存中的df,监控逻辑永远使用服务启动时的旧规则。
解决方案
1. 修复线程配置
创建线程时添加daemon=True参数,确保主进程退出时子线程同步销毁:
thread = threading.Thread(target=price_watch, daemon=True) thread.start()
2. 启用Django开发服务器多线程模式
启动服务时添加--threaded参数,让主线程可以独立处理HTTP请求:
python manage.py runserver --threaded
3. 优化监控逻辑降低CPU占用
把告警规则按交易对分组存储,避免每次循环全局遍历pandas表,同时每次循环刷新告警规则:
def price_watch(): interval = 10 while True: # 每次循环刷新最新告警规则 alerts = Alerts.objects.all().values() df = pd.DataFrame(alerts) # 按交易对分组生成规则字典,减少后续筛选开销 alert_map = df.groupby('symbol').apply(lambda x: x.to_dict('records')).to_dict() coins = client.get_all_tickers() for coin in coins: symbol = coin['symbol'] price = float(coin['price']) # 仅处理有告警规则的交易对 if symbol not in alert_map: continue for rule in alert_map[symbol]: if (rule['direction'] == 'ABOVE' and price > rule['price']) or (rule['direction'] == 'BELOW' and price < rule['price']): print(f"触发告警:{symbol} 价格{price} 达到{rule['direction']} {rule['price']}") time.sleep(interval)
4. 生产环境替代方案
如果是线上部署,不要用内置threading做后台常驻任务,改用成熟的任务调度框架:
- 定时任务场景可以用Celery Beat + Redis/RabbitMQ
- 轻量场景可以用Django-Q、APScheduler集成到Django中
内容的提问来源于stack exchange,提问作者Kadiem Alqazzaz
相关产品推荐
相关产品推荐

