我最近開發個智能控制系統,包括實時數據采集、預測、策略優化等功能,最近增加在線學習功能,也就是在線進行模型訓練,在線進行模型訓練時出現了問題,現象為:
控制臺報:
- cmdstanpy - INFO - Chain [1] start processing
- 所有任務、線程停止,Web服務登錄無法訪問
- 后臺的python進行仍在,CPU資源降低到10%以下(訓練時都在70%到90%)
環境信息:
- CentOS操作系統
- Python 3.8
- apscheduler 任務管理
- python 線程
- python 進程 multiprocessing
- Flask提供Web服務
- 機器學習使用Scikit-learn,時序預測使用Prophet
1. 原因分析
1.1. 我的初步分析
直接原因是我在線學習功能設計忽略了資源爭用的情景,只考慮到在線訓練是偶爾發生的事。
1.2. 根本原因分析
再看看DeepSeek給出分析原因:
(1) CPU 資源耗盡
- Scikit-learn 和 Prophet 默認會占用所有可用的 CPU 核心(尤其是 n_jobs=-1 時)。
- 多線程任務(如 Flask 請求、APScheduler)因 CPU 爭搶被餓死。
(2) 內存不足(OOM)
- 大規模數據訓練時,內存被占滿,觸發系統 OOM Killer 殺死進程或頻繁換頁(Swapping),導致卡頓。
(3) Python 的 GIL(全局解釋器鎖)
- Prophet 和 Scikit-learn 的部分計算是 C++ 并行的,但 Python 線程仍受 GIL 限制,導致線程調度阻塞。
(4) 磁盤/文件 I/O 阻塞
- Prophet 會生成臨時文件(如 Stan 模型的編譯緩存),高負載時磁盤 I/O 成為瓶頸。
(5) 線程死鎖
- 如果任務之間有共享資源的鎖競爭(如數據庫連接、文件讀寫),可能導致死鎖。
2. 解決方案
2.1. 啟動子進程進行訓練
我在進行在線機器學習訓練時,使用“multiprocessing.Process”,啟動進程進行訓練。
在使用 multiprocessing.Process 啟動子進程進行機器學習訓練時,確實可以避免 GIL 鎖競爭 和 主線程阻塞 的問題。
2.1.1. 解決方案(兼容 Windows + Flask)
方法 1:將多進程代碼封裝到單獨模塊
(1) 創建獨立模塊 train_worker.py
# train_worker.py
from multiprocessing import Process, Queue
# 啟動多進程訓練
def run_train(model_type, model_param):"""子進程實際執行的函數"""if model_type == '24h':return LoadMLPModelTrain(model_param)elif model_type == 'realtime':return LoadRealTimeMLPModelTrain(model_param)def _worker_proc(q: Queue, model_type, model_param):"""替代 lambda 的全局函數"""result = run_train(model_type, model_param) # 調用實際訓練函數q.put(result)def async_train(model_type, model_param):"""Flask 調用的接口"""result_queue = Queue()p = Process(target=_worker_proc, # 使用全局函數而非 lambdaargs=(result_queue, model_type, model_param))p.start()ret = result_queue.get() # 阻塞等待結果p.join()return ret