From 022dd77ea9997a34001244384d4648de37f94105 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Wed, 7 Jan 2026 00:35:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(thread-safety):=20=E5=B7=B2=E4=B8=BA=20temp?= =?UTF-8?q?lates.py=20=20=E4=B8=AD=E7=9A=84=20Jinja2=20=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=B7=BB=E5=8A=A0=E7=BA=BF=E7=A8=8B=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E4=BF=9D=E6=8A=A4=EF=BC=8C=E5=85=B7=E4=BD=93=E6=94=B9?= =?UTF-8?q?=E5=8A=A8=E5=A6=82=E4=B8=8B=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用双重检查锁定(Double-Checked Locking)保护缓存访问 读取缓存时加锁,避免多线程同时读取时的数据竞争 创建新环境后再次加锁检查,防止高并发下重复创建相同模板的环境 原代码中 _envs 字典会通过 asyncio.to_thread(self._get_env_sync) 被多个线程同时访问和修改,存在数据竞争风险。现在通过 threading.Lock 确保了线程安全。 --- src/reports/templates.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/reports/templates.py b/src/reports/templates.py index 1f7924e..d422919 100644 --- a/src/reports/templates.py +++ b/src/reports/templates.py @@ -5,6 +5,7 @@ HTML模板模块 import asyncio import os +import threading from jinja2 import Environment, FileSystemLoader, select_autoescape @@ -19,16 +20,19 @@ class HTMLTemplates: self.config_manager = config_manager # 设置模板根目录 self.base_dir = os.path.join(os.path.dirname(__file__), "templates") - # 缓存不同模板的Jinja2环境 + # 缓存不同模板的Jinja2环境(多线程安全) self._envs = {} + self._env_lock = threading.Lock() def _get_env_sync(self) -> Environment: """获取当前配置的模板环境(同步版本,供 asyncio.to_thread 调用)""" template_name = self.config_manager.get_report_template() - # 如果环境已缓存且配置未变 - if template_name in self._envs: - return self._envs[template_name] + # 如果环境已缓存且配置未变(使用锁保证多线程安全) + with self._env_lock: + env = self._envs.get(template_name) + if env is not None: + return env template_dir = os.path.join(self.base_dir, template_name) if not os.path.exists(template_dir): @@ -41,7 +45,14 @@ class HTMLTemplates: trim_blocks=True, lstrip_blocks=True, ) - self._envs[template_name] = env + + # 使用双重检查锁定,避免在高并发下重复创建相同 template_name 的 env + with self._env_lock: + existing = self._envs.get(template_name) + if existing is not None: + return existing + self._envs[template_name] = env + return env async def _get_env_async(self) -> Environment: