ThreadpoolController导致Flask(Passenger_wsgi)加载Numpy失败
问题背景
在A2 Hosting共享主机上部署Flask应用,其中包含一个用sklearn KMeans实现图片减色的脚本。之前脚本运行时因线程创建失败报错,用threadpoolctl给simplify_image函数加了单线程限制后,脚本在命令行能正常运行,但Flask应用重启后加载失败。日志显示导入numpy阶段,OpenBLAS尝试创建过多线程失败,最终触发KeyboardInterrupt。而未添加线程限制时,Flask能正常启动。
解决方案
1. 全局限制OpenBLAS/OMP线程数(最直接有效)
问题核心是numpy依赖的OpenBLAS在初始化时,默认会创建与CPU核心数一致的线程,而共享主机的进程数限制不允许这么多线程。在模块导入前设置环境变量,强制OpenBLAS只用1个线程:
修改simplify_image_to_approved_colors.py的最顶部:
import os # 强制OpenBLAS和OpenMP仅使用1个线程 os.environ['OPENBLAS_NUM_THREADS'] = '1' os.environ['OMP_NUM_THREADS'] = '1' # 之后再导入其他库 import numpy as np from sklearn.cluster import KMeans ... # 剩余导入语句
这样numpy初始化时就会使用单线程,不会触发线程创建失败的问题。
2. 将线程限制移到函数内部,避免模块导入阶段冲突
之前用ThreadpoolController装饰器会在模块导入时就生效,可能与共享主机的线程管理机制冲突。改成仅在函数执行时临时限制线程:
修改simplify_image_to_approved_colors.py:
# 删除以下两行代码: # controller = ThreadpoolController() # @controller.wrap(limits=1, user_api='openmp') def simplify_image(pattern_path, simplified_image_path, image_path, num_colors, valid_colors=np.array([color[2] for color in dmc_colors_list.dmc_colors]), target_width=None, line_spacing=14, is_circle=False, verbose=False): # 仅在函数执行期间限制线程 with threadpool_limits(limits=1, user_api='openmp'): global image_name image_name = image_path.split("/")[-1].split(".")[0] target_width = target_width * line_spacing # 原函数内的所有逻辑都放在这个with代码块中 image = Image.open(image_path) if target_width is not None: aspect_ratio = image.width / image.height target_height = int(target_width / aspect_ratio) image = image.resize((target_width, target_height)) ... # 剩余图片处理逻辑
这样模块导入阶段不会触发线程限制,numpy能正常初始化,仅在处理图片时才启用单线程限制,既解决脚本运行时的线程问题,又不影响Flask应用启动。
3. 调整Passenger进程配置(若有权限操作)
共享主机上Passenger默认可能启动多个应用进程,每个进程又会尝试创建多个线程,导致总线程数超出限制。在应用根目录创建passenger.ini文件,设置进程数为1:
passenger_max_pool_size 1 passenger_min_instances 1
减少Passenger的进程数,降低整体资源占用,避免触发主机的线程/进程限制。
内容的提问来源于stack exchange,提问作者Troy D

