问题原因
并发实例限制触发
APScheduler默认max_instances=1
,当任务执行时间超过间隔时间(如30秒)时,新触发的实例会被拒绝。
任务执行时间过长
当前任务可能包含阻塞操作(如网络请求、复杂计算等),导致无法在30秒内完成通俗的讲:
就是当同一个job(同一job_id)上一次执行的程序还没有执行完,下一次的trigger就来了,导致不能并行的执行多次,从而阻塞
解决方案分析:
这里和两个参数有关
参数1:max_instances
max_instances 这个参数可以允许同一job在同时并行,就算上次的没结束,也可以新开一个线程去处理新来的请求
max_instances
控制的是同一任务的并发实例数,每个实例会占用线程池中的一个线程
#即使线程池有空闲线程,该任务最多同时运行2个实例
scheduler.add_job(func, 'interval', seconds=30, max_instances=2)
#两个任务实例数独立统计,不会互相限制
scheduler.add_job(func, 'interval', id='job1', seconds=10)
scheduler.add_job(func, 'interval', id='job2', seconds=20)
参数2:max_works
max_works 这个参数是可以开的最大线程数,控制线程池容量。有任务(无论是否属于同一 job_id
)共享该线程池资源。即使某个任务设置 max_instances=3
,若线程池仅允许同时运行2个线程,实际最多只能并行执行2个该任务的实例
若未显式指定 max_workers,其默认值遵循以下规则:
default_max_workers = min(32, os.cpu_count() + 4)
其中:
os.cpu_count():获取当前系统的 CPU 核心数
32 是默认上限,即使 CPU 核心数较多(如服务器场景),最大线程数也不会超过此值
设置线程池
executor = ThreadPoolExecutor(max_workers=2)
那么如何设置max_works呢
方案1 根据总并发需求设置 max_workers
,建议公式: max_workers = SUM(各任务 max_instances) + 缓冲线程数
方案2 I/O 密集型:max_workers = CPU核心数 × 2
import os
cpu_cores = os.cpu_count()
io_max_workers = cpu_cores * 2 # I/O 密集型推荐值
方案3 CPU 密集型: max_workers = CPU核心数
import os
cpu_cores = os.cpu_count()
cpu_max_workers = cpu_cores # CPU 密集型推荐值
参数对比
参数 | 作用层级 | 限制对象 | 优先级关系 |
---|---|---|---|
max_workers |
线程池级 | 全局并行线程总数 | 若线程池空闲线程不足,即使 max_instances 允许,实例也无法执行 |
max_instances |
任务级 | 单个任务的并发实例数 | 仅在有空闲线程时生效,不能突破线程池限制 |
条件组合 | 结果 |
---|---|
max_instances≥可用线程数 |
实际并行度=可用线程数 |
max_instances<可用线程数 |
实际并行度=max_instances |
自动释放机制
那么不人工干预,是否可以主动释放呢,结论如下
主动释放机制
max_instances
或线程池资源不足导致实例被跳过,后续触发需等待正在运行的实例结束释放资源,而非直接抛弃或自动重置限制类型 | 释放触发条件 | 恢复时间点 |
---|---|---|
max_instances |
当前运行实例完成 | 下一周期触发时重新检查 |
线程池资源 | 线程归还至线程池 | 任务结束后立即释放 |
scheduler.add_job( long_task, # 耗时任务(例如持续30秒) 'interval', seconds=10, max_instances=1 # 默认配置 )
max_instances=1
被跳过(报错)
executor = ThreadPoolExecutor(max_workers=2) # 全局仅2线程 scheduler.add_job(task1, 'interval', seconds=5, max_instances=3) scheduler.add_job(task2, 'interval', seconds=5, max_instances=3)
解决:
1 调整任务参数
max_instances
或延长任务间隔(seconds
/minutes
)以适应任务执行时间misfire_grace_time
容忍短暂延迟,避免高频触发导致堆scheduler.add_job(
task,
'interval',
seconds=10,
max_instances=3,
misfire_grace_time=60 # 允许延迟60秒内补执行
)
2 扩展执行资源
提升线程池容量 max_workers
以匹配并发需求
max_workers
可能导致线程争抢资源,反而降低吞吐量。from concurrent.futures import ThreadPoolExecutor
# 手动设置 max_workers=200(需评估系统资源)
executor = ThreadPoolExecutor(max_workers=200)