Skip to content

Commit 215d3c1

Browse files
committed
feat: 添加 AppInjector 单例模式及相关示例
- 新增 AppInjector 类,确保整个应用程序使用同一个依赖注入器实例 - 实现线程安全的单例模式 - 添加多个示例以展示如何使用 AppInjector 进行依赖注入 - 更新相关文档以说明单例模式的使用和优势 - 引入租户中间件以支持多租户环境
1 parent 264d6e6 commit 215d3c1

14 files changed

Lines changed: 1355 additions & 9 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# 单例模式中选择 threading.Lock 的设计决策
2+
3+
## 🤔 问题背景
4+
5+
在实现 FastAPI Keystone 的单例模式时,我们面临一个重要的技术选择:使用 `threading.Lock` 还是 `asyncio.Lock` 来确保线程安全?
6+
7+
## 🎯 设计目标
8+
9+
1. **通用兼容性**:支持同步和异步环境
10+
2. **简单易用**:不增加不必要的复杂性
11+
3. **高性能**:适合单例创建的使用场景
12+
4. **可靠性**:经过实战验证的技术方案
13+
14+
## ⚖️ 技术对比
15+
16+
### threading.Lock vs asyncio.Lock
17+
18+
| 特性 | threading.Lock | asyncio.Lock |
19+
|------|---------------|--------------|
20+
| 使用环境 | 同步 + 异步 | 仅异步 |
21+
| 语法要求 | 简单 `with` | 需要 `async with` |
22+
| 依赖关系 | 标准库,无额外依赖 | 需要事件循环 |
23+
| 性能开销 || 稍高(异步调度) |
24+
| 学习成本 || 中等 |
25+
| 适用场景 | 通用 | 异步密集型 |
26+
27+
## 🔍 具体原因分析
28+
29+
### 1. **兼容性需求**
30+
31+
AppInjector 需要在多种场景中使用:
32+
33+
```python
34+
# 场景1: 应用启动(同步)
35+
def create_app():
36+
app = FastAPI()
37+
injector = AppInjector([DatabaseModule()]) # 必须支持同步
38+
return app
39+
40+
# 场景2: 依赖注入(可能同步或异步)
41+
def get_injector() -> AppInjector:
42+
return AppInjector([AppModule()]) # 需要兼容性
43+
44+
# 场景3: 异步请求处理
45+
async def api_handler(injector: AppInjector = Depends(get_injector)):
46+
service = injector.get_instance(UserService) # 实例获取通常是同步的
47+
return await service.get_users()
48+
```
49+
50+
**如果使用 asyncio.Lock 的问题:**
51+
```python
52+
# ❌ 这样会导致语法错误
53+
def create_app():
54+
injector = await AppInjector([DatabaseModule()]) # SyntaxError! 同步函数不能用await
55+
56+
# ❌ 强制使用异步会让API变得复杂
57+
async def get_injector() -> AppInjector:
58+
return await AppInjector([AppModule()]) # 不必要的异步化
59+
```
60+
61+
### 2. **使用场景特征**
62+
63+
单例模式的典型使用特点:
64+
65+
- **低频操作**:通常在应用启动时创建一次
66+
- **短暂锁定**:锁持有时间极短(仅实例创建期间)
67+
- **非IO密集**:不涉及文件、网络等异步IO操作
68+
- **混合环境**:需要在同步和异步代码中都能使用
69+
70+
这些特征使得 `threading.Lock` 更合适:
71+
72+
```python
73+
# threading.Lock 适合的场景
74+
with self._lock: # 快速获取锁
75+
if cls not in instances:
76+
instances[cls] = cls(*args, **kwargs) # 快速创建实例
77+
# 立即释放锁,不阻塞其他操作
78+
```
79+
80+
### 3. **性能考虑**
81+
82+
```python
83+
# 性能测试结果(从示例中)
84+
"""
85+
单线程创建100个实例用时: 0.0000秒
86+
5线程并发创建500个实例用时: 0.0004秒
87+
所有实例都相同: True
88+
"""
89+
```
90+
91+
`threading.Lock` 在单例场景中表现出色:
92+
- **微秒级锁定**:创建实例的时间极短
93+
- **低开销**:不需要异步调度器
94+
- **高并发**:支持多线程并发访问
95+
96+
### 4. **代码简洁性**
97+
98+
```python
99+
# threading.Lock 版本 - 简洁直观
100+
def singleton(cls):
101+
instances = {}
102+
lock = threading.Lock()
103+
104+
def get_instance(*args, **kwargs):
105+
if cls not in instances:
106+
with lock:
107+
if cls not in instances:
108+
instances[cls] = cls(*args, **kwargs)
109+
return instances[cls]
110+
111+
return get_instance
112+
113+
# asyncio.Lock 版本 - 复杂且限制多
114+
def async_singleton(cls):
115+
instances = {}
116+
lock = asyncio.Lock()
117+
118+
async def get_instance(*args, **kwargs): # 必须是异步函数
119+
if cls not in instances:
120+
async with lock: # 必须用 async with
121+
if cls not in instances:
122+
instances[cls] = cls(*args, **kwargs)
123+
return instances[cls]
124+
125+
return get_instance # 返回的是异步函数,使用时必须 await
126+
```
127+
128+
## 🏗️ 架构优势
129+
130+
### 统一的编程模型
131+
```python
132+
# 使用 threading.Lock,API 保持一致
133+
injector = AppInjector([Module()]) # 同步和异步环境都可以这样用
134+
service = injector.get_instance(Service) # 实例获取也是同步的
135+
136+
# 如果使用 asyncio.Lock,API 会分裂
137+
injector = await AppInjector.create([Module()]) # 异步创建
138+
service = await injector.get_instance(Service) # 异步获取,增加复杂性
139+
```
140+
141+
### 更好的错误处理
142+
```python
143+
# threading.Lock - 错误处理简单
144+
try:
145+
injector = AppInjector([Module()])
146+
except Exception as e:
147+
logger.error(f"Failed to create injector: {e}")
148+
149+
# asyncio.Lock - 错误处理复杂
150+
try:
151+
injector = await AppInjector.create([Module()])
152+
except Exception as e:
153+
logger.error(f"Failed to create injector: {e}")
154+
# 还需要处理异步上下文错误
155+
```
156+
157+
## 🔬 技术深度分析
158+
159+
### 并发模型差异
160+
161+
**threading.Lock**
162+
- 基于操作系统线程
163+
- 抢占式调度
164+
- 适合 CPU 密集和混合场景
165+
- 与 FastAPI 的线程池集成良好
166+
167+
**asyncio.Lock**
168+
- 基于协程调度
169+
- 协作式调度
170+
- 适合 IO 密集场景
171+
- 需要整个调用链都是异步的
172+
173+
### 内存和性能开销
174+
175+
```python
176+
# threading.Lock 开销分析
177+
threading.Lock() # 系统级锁,开销极小
178+
with lock: # 直接系统调用,快速
179+
pass # 操作完成,立即释放
180+
181+
# asyncio.Lock 开销分析
182+
asyncio.Lock() # 需要事件循环支持
183+
async with lock: # 需要异步调度器介入
184+
await ... # 可能涉及协程切换
185+
```
186+
187+
## 📊 实际测试数据
188+
189+
基于 `examples/advanced/lock-comparison/main.py` 的测试结果:
190+
191+
| 测试场景 | threading.Lock | asyncio.Lock |
192+
|---------|---------------|--------------|
193+
| 同步环境使用 | ✅ 完美支持 | ❌ 无法使用 |
194+
| 异步环境使用 | ✅ 完美支持 | ✅ 支持 |
195+
| 混合环境 | ✅ 无缝切换 | ❌ 需要API分离 |
196+
| 100次创建耗时 | ~0.0000秒 | ~0.0001秒 |
197+
| 500次并发耗时 | ~0.0004秒 | 不适用 |
198+
199+
## 🎯 最终决策
200+
201+
**选择 `threading.Lock` 的原因总结:**
202+
203+
1. **✅ 通用兼容性**:同步异步环境都可用
204+
2. **✅ API 简洁性**:不需要 await 语法
205+
3. **✅ 性能优秀**:微秒级锁定,低开销
206+
4. **✅ 学习成本低**:使用简单,易于理解
207+
5. **✅ 生态兼容**:与 FastAPI 生态完美集成
208+
6. **✅ 维护简单**:代码路径少,bug 风险低
209+
210+
**asyncio.Lock 的局限性:**
211+
212+
1. **❌ 环境限制**:只能在异步上下文使用
213+
2. **❌ API 复杂**:强制异步化简单操作
214+
3. **❌ 性能开销**:异步调度器介入
215+
4. **❌ 学习门槛**:需要深度理解异步编程
216+
5. **❌ 生态割裂**:与同步代码无法兼容
217+
218+
## 💡 设计哲学
219+
220+
> **"选择最适合的工具,而不是最新的工具"**
221+
222+
在单例模式中,我们的目标是:
223+
- 确保唯一性(✅ 两种锁都能做到)
224+
- 线程安全(✅ 两种锁都能做到)
225+
- 使用简单(✅ threading.Lock 更胜一筹)
226+
- 性能优秀(✅ threading.Lock 更适合)
227+
- 通用兼容(✅ threading.Lock 独有优势)
228+
229+
因此,`threading.Lock` 是单例模式的最佳选择。
230+
231+
## 🔄 何时考虑 asyncio.Lock
232+
233+
如果你的使用场景满足以下条件,可以考虑 `asyncio.Lock`
234+
235+
1. **纯异步环境**:整个应用都是异步的
236+
2. **IO 密集操作**:单例创建涉及异步 IO
237+
3. **长时间锁定**:需要在锁内执行异步操作
238+
4. **协程优化**:需要避免阻塞事件循环
239+
240+
```python
241+
# 适合 asyncio.Lock 的场景示例
242+
class AsyncDatabaseSingleton:
243+
async def __new__(cls):
244+
async with cls._lock:
245+
if cls._instance is None:
246+
cls._instance = super().__new__(cls)
247+
await cls._instance._async_init() # 异步初始化
248+
return cls._instance
249+
250+
async def _async_init(self):
251+
# 建立异步数据库连接
252+
self.connection = await asyncpg.connect(DATABASE_URL)
253+
```
254+
255+
但对于 FastAPI Keystone 的 AppInjector,这些条件都不满足,所以 `threading.Lock` 是正确选择。
256+
257+
## 📚 参考资料
258+
259+
- [Python threading.Lock 文档](https://docs.python.org/3/library/threading.html#lock-objects)
260+
- [Python asyncio.Lock 文档](https://docs.python.org/3/library/asyncio-sync.html#asyncio.Lock)
261+
- [FastAPI 并发模型](https://fastapi.tiangolo.com/async/)
262+
- [Single-threaded vs Multi-threaded in Python](https://realpython.com/python-concurrency/)

0 commit comments

Comments
 (0)