一、服务器
"""
简单的演示服务器 - 用于并发测试
模拟一个有延迟的API服务
"""
from fastapi import FastAPI, Form
import asyncio
import time
from datetime import datetime
app = FastAPI()
# 用于统计并发请求数
concurrent_requests = 0
@app.post("/process")
async def process_request(
user_id: str = Form(...),
data: str = Form(...)
):
"""
模拟一个需要处理时间的API
返回处理结果
"""
global concurrent_requests
concurrent_requests += 1
start_time = time.time()
current_concurrent = concurrent_requests
print(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] "
f"收到请求 user_id={user_id}, 当前并发数={current_concurrent}")
# 模拟I/O操作(如数据库查询、文件读取等)
# 使用 asyncio.sleep 而不是 time.sleep,这样可以释放事件循环
await asyncio.sleep(1.0) # 模拟1秒的处理时间
concurrent_requests -= 1
elapsed = time.time() - start_time
result = {
"code": 1000,
"msg": "处理成功",
"data": {
"user_id": user_id,
"input_data": data,
"process_time": f"{elapsed:.3f}秒",
"concurrent_at_start": current_concurrent
}
}
print(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] "
f"完成请求 user_id={user_id}, 耗时={elapsed:.3f}秒")
return result
@app.get("/status")
async def get_status():
"""获取服务器状态"""
return {
"status": "running",
"current_concurrent": concurrent_requests,
"timestamp": datetime.now().isoformat()
}
if __name__ == "__main__":
import uvicorn
print("=" * 70)
print("🚀 演示服务器启动")
print("=" * 70)
print("服务地址: http://localhost:9000")
print("测试接口: POST /process")
print("状态接口: GET /status")
print("=" * 70)
uvicorn.run(app, host="0.0.0.0", port=9000)
方式1: 线程池并发
"""
并发客户端 - 方式1: 线程池并发
使用 ThreadPoolExecutor + requests 库
"""
import requests
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from typing import List, Dict
class ThreadPoolClient:
"""使用线程池的并发客户端"""
def __init__(self, base_url: str = "http://localhost:8000"):
self.base_url = base_url
def send_request(self, user_id: str, data: str) -> Dict:
"""
发送单个请求(这个函数会在单独的线程中运行)
使用 requests 库(同步HTTP库)
"""
url = f"{self.base_url}/process"
try:
start_time = time.time()
# requests.post 是阻塞操作
# 当前线程会等待HTTP响应返回
response = requests.post(
url,
data={
"user_id": user_id,
"data": data
},
timeout=10
)
elapsed = time.time() - start_time
result = response.json()
return {
"success": True,
"user_id": user_id,
"elapsed": elapsed,
"response": result
}
except Exception as e:
return {
"success": False,
"user_id": user_id,
"elapsed": 0,
"error": str(e)
}
def run_concurrent_test(self, num_requests: int, max_workers: int):
"""
使用线程池执行并发测试
Args:
num_requests: 总请求数
max_workers: 线程池大小(最大并发线程数)
"""
print("\n" + "=" * 70)
print(f"🧵 线程池并发测试")
print("=" * 70)
print(f"请求总数: {num_requests}")
print(f"线程池大小: {max_workers}")
print(f"开始时间: {datetime.now().strftime('%H:%M:%S')}")
print("=" * 70)
# 创建线程池
# max_workers 指定最多同时运行多少个线程
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务到线程池
futures = []
start_time = time.time()
for i in range(num_requests):
# submit() 立即返回一个 Future 对象
# 实际的函数执行会在线程池的线程中进行
future = executor.submit(
self.send_request,
user_id=f"user_{i:03d}",
data=f"测试数据_{i}"
)
futures.append(future)
print(f"[提交] 任务 {i+1}/{num_requests}")
print(f"\n所有任务已提交,等待执行完成...\n")
# 收集结果
results = []
completed_count = 0
# as_completed() 会按完成顺序返回 Future 对象
for future in as_completed(futures):
result = future.result() # 获取线程执行结果
results.append(result)
completed_count += 1
if result["success"]:
print(f"[完成 {completed_count}/{num_requests}] "
f"{result['user_id']} - 耗时: {result['elapsed']:.3f}秒")
else:
print(f"[失败 {completed_count}/{num_requests}] "
f"{result['user_id']} - 错误: {result['error']}")
total_time = time.time() - start_time
# 统计结果
self._print_statistics(results, total_time)
return results
def _print_statistics(self, results: List[Dict], total_time: float):
"""打印统计信息"""
success_results = [r for r in results if r["success"]]
failed_count = len(results) - len(success_results)
print("\n" + "=" * 70)
print("📊 测试结果统计")
print("=" * 70)
print(f"总请求数: {len(results)}")
print(f"成功数: {len(success_results)}")
print(f"失败数: {failed_count}")
print(f"总耗时: {total_time:.3f} 秒")
if success_results:
avg_time = sum(r["elapsed"] for r in success_results) / len(success_results)
print(f"平均响应时间: {avg_time:.3f} 秒")
print(f"QPS: {len(results)/total_time:.2f} 请求/秒")
print("=" * 70)
def main():
print("=" * 70)
print("🧵 线程池并发客户端演示")
print("=" * 70)
client = ThreadPoolClient("http://localhost:9000")
# 测试1: 10个请求,5个线程
print("\n### 测试1: 10个请求,线程池大小=5")
client.run_concurrent_test(num_requests=10, max_workers=5)
time.sleep(2)
# 测试2: 10个请求,10个线程
print("\n### 测试2: 10个请求,线程池大小=10")
client.run_concurrent_test(num_requests=10, max_workers=10)
if __name__ == "__main__":
main()
结果:
======================================================================
🧵 线程池并发客户端演示
======================================================================### 测试1: 10个请求,线程池大小=5
======================================================================
🧵 线程池并发测试
======================================================================
请求总数: 10
线程池大小: 5
开始时间: 05:43:26
======================================================================
[提交] 任务 1/10
[提交] 任务 2/10
[提交] 任务 3/10
[提交] 任务 4/10
[提交] 任务 5/10
[提交] 任务 6/10
[提交] 任务 7/10
[提交] 任务 8/10
[提交] 任务 9/10
[提交] 任务 10/10所有任务已提交,等待执行完成...
[完成 1/10] user_003 - 耗时: 1.009秒
[完成 2/10] user_000 - 耗时: 1.027秒
[完成 3/10] user_002 - 耗时: 1.027秒
[完成 4/10] user_001 - 耗时: 1.034秒
[完成 5/10] user_004 - 耗时: 1.022秒
[完成 6/10] user_006 - 耗时: 1.011秒
[完成 7/10] user_005 - 耗时: 1.017秒
[完成 8/10] user_007 - 耗时: 1.010秒
[完成 9/10] user_008 - 耗时: 1.011秒
[完成 10/10] user_009 - 耗时: 1.007秒======================================================================
📊 测试结果统计
======================================================================
总请求数: 10
成功数: 10
失败数: 0
总耗时: 2.049 秒
平均响应时间: 1.018 秒
QPS: 4.88 请求/秒
======================================================================### 测试2: 10个请求,线程池大小=10
======================================================================
🧵 线程池并发测试
======================================================================
请求总数: 10
线程池大小: 10
开始时间: 05:43:30
======================================================================
[提交] 任务 1/10
[提交] 任务 2/10
[提交] 任务 3/10
[提交] 任务 4/10
[提交] 任务 5/10
[提交] 任务 6/10
[提交] 任务 7/10
[提交] 任务 8/10
[提交] 任务 9/10
[提交] 任务 10/10所有任务已提交,等待执行完成...
[完成 1/10] user_002 - 耗时: 1.008秒
[完成 2/10] user_004 - 耗时: 1.007秒
[完成 3/10] user_000 - 耗时: 1.022秒
[完成 4/10] user_001 - 耗时: 1.021秒
[完成 5/10] user_003 - 耗时: 1.015秒
[完成 6/10] user_005 - 耗时: 1.012秒
[完成 7/10] user_007 - 耗时: 1.007秒
[完成 8/10] user_006 - 耗时: 1.012秒
[完成 9/10] user_008 - 耗时: 1.007秒
[完成 10/10] user_009 - 耗时: 1.005秒======================================================================
📊 测试结果统计
======================================================================
总请求数: 10
成功数: 10
失败数: 0
总耗时: 1.037 秒
平均响应时间: 1.012 秒
QPS: 9.64 请求/秒
======================================================================
## 🎯 可视化时间轴
### 测试1: 5个线程池,10个请求
```
时间: 0秒 1秒 2秒
├──────────────────┼──────────────────┤
线程1 [════ 请求1 ════>]
线程2 [════ 请求2 ════>]
线程3 [════ 请求3 ════>]
线程4 [════ 请求4 ════>]
线程5 [════ 请求5 ════>]
↓ 线程释放,接收新任务
线程1 [════ 请求6 ════>]
线程2 [════ 请求7 ════>]
线程3 [════ 请求8 ════>]
线程4 [════ 请求9 ════>]
线程5 [════ 请求10 ═══>]
└─ 第一批(1秒)─┘└─ 第二批(1秒)─┘
总耗时: 约2秒
```
**关键点**:
- ✅ 前5个请求**并发**执行(不是串行)
- ✅ 后5个请求**等待**前5个完成后才能开始
- ✅ 总耗时 = 批次数 × 单次耗时 = 2 × 1秒 = 2秒
### 测试2: 10个线程池,10个请求
```
时间: 0秒 1秒
├──────────────────┤
线程1 [════ 请求1 ════>]
线程2 [════ 请求2 ════>]
线程3 [════ 请求3 ════>]
线程4 [════ 请求4 ════>]
线程5 [════ 请求5 ════>]
线程6 [════ 请求6 ════>]
线程7 [════ 请求7 ════>]
线程8 [════ 请求8 ════>]
线程9 [════ 请求9 ════>]
线程10 [════ 请求10 ═══>]
└─ 全部并发(1秒)┘
总耗时: 约1秒
```
**关键点**:
- ✅ 所有10个请求**同时**并发执行
- ✅ 没有等待
- ✅ 总耗时 = 单次耗时 = 1秒
说明:
╔══════════════════════════════════════════════════════════════════════════════╗
║ 你的测试结果可视化分析 ║
╚══════════════════════════════════════════════════════════════════════════════╝
测试场景:10个请求,每个请求服务器处理时间1秒
┌──────────────────────────────────────────────────────────────────────────────┐
│ 测试1: 线程池大小=5 │
└──────────────────────────────────────────────────────────────────────────────┘
❓ 如果是串行执行(一个接一个)会怎样?
时间: 0s 1s 2s 3s 4s 5s 6s 7s 8s 9s 10s
├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
请求1 [═══>]
请求2 [═══>]
请求3 [═══>]
请求4 [═══>]
请求5 [═══>]
请求6 [═══>]
请求7 [═══>]
请求8 [═══>]
请求9 [═══>]
请求10 [═══>]
总耗时: 10秒(太慢了!)
✅ 实际使用5个线程并发执行:
时间: 0秒 1秒 2秒
├────────────────┼────────────────┤
第一批(5个线程同时工作):
线程1 [user_003: 1.009s────────>] ✓
线程2 [user_000: 1.027s────────>] ✓
线程3 [user_002: 1.027s────────>] ✓
线程4 [user_001: 1.034s────────>] ✓
线程5 [user_004: 1.022s────────>] ✓
↓ 线程空闲,接收新任务
第二批(5个线程同时工作):
线程1 [user_006: 1.011s────>] ✓
线程2 [user_005: 1.017s────>] ✓
线程3 [user_007: 1.010s────>] ✓
线程4 [user_008: 1.011s────>] ✓
线程5 [user_009: 1.007s────>] ✓
总耗时: 2.049秒(比串行快5倍!)
═══════════════════════════════════════════════════════════════════════════════
计算验证:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
如果串行: 10个请求 × 1秒/请求 = 10秒
实际并发: ⌈10请求 ÷ 5线程⌉ × 1秒 = 2批 × 1秒 = 2秒 ✓
性能提升: 10秒 ÷ 2秒 = 5倍加速
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
关键点:
✓ 第一批5个请求在 0-1秒 之间并发执行
✓ 第二批5个请求在 1-2秒 之间并发执行
✓ 总耗时 ≠ 所有请求耗时之和 (不是10秒)
✓ 总耗时 = 批次数 × 每批耗时 (2批 × 1秒)
┌──────────────────────────────────────────────────────────────────────────────┐
│ 测试2: 线程池大小=10 │
└──────────────────────────────────────────────────────────────────────────────┘
✅ 使用10个线程并发执行:
时间: 0秒 1秒
├────────────────┤
所有请求同时执行(10个线程全部工作):
线程1 [user_002: 1.008s──────>] ✓
线程2 [user_004: 1.007s──────>] ✓
线程3 [user_000: 1.022s──────>] ✓
线程4 [user_001: 1.021s──────>] ✓
线程5 [user_003: 1.015s──────>] ✓
线程6 [user_005: 1.012s──────>] ✓
线程7 [user_007: 1.007s──────>] ✓
线程8 [user_006: 1.012s──────>] ✓
线程9 [user_008: 1.007s──────>] ✓
线程10 [user_009: 1.005s──────>] ✓
总耗时: 1.037秒(比串行快10倍!)
═══════════════════════════════════════════════════════════════════════════════
计算验证:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
如果串行: 10个请求 × 1秒/请求 = 10秒
实际并发: ⌈10请求 ÷ 10线程⌉ × 1秒 = 1批 × 1秒 = 1秒 ✓
性能提升: 10秒 ÷ 1秒 = 10倍加速
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
关键点:
✓ 所有10个请求同时并发执行
✓ 没有等待,一批完成
✓ 耗时范围很小 (1.005s - 1.022s,仅17ms差异)
✓ 接近理想的完全并行
╔══════════════════════════════════════════════════════════════════════════════╗
║ 对比总结 ║
╚══════════════════════════════════════════════════════════════════════════════╝
┌────────────────┬───────────┬───────────┬───────────┬───────────┐
│ │ 串行执行 │ 5线程池 │ 10线程池 │ 性能对比 │
├────────────────┼───────────┼───────────┼───────────┼───────────┤
│ 批次数 │ 10 │ 2 │ 1 │ │
│ 总耗时 │ 10秒 │ 2.049秒 │ 1.037秒 │ │
│ QPS │ 1.0 │ 4.88 │ 9.64 │ │
│ vs串行加速 │ 1x │ 4.9x │ 9.6x │ 📈 巨大提升│
│ vs 5线程加速 │ — │ 1x │ 2.0x │ 📊 翻倍 │
└────────────────┴───────────┴───────────┴───────────┴───────────┘
════════════════════════════════════════════════════════════════════════════════
🔑 核心理解
1️⃣ 为什么总耗时不是10秒?
✓ 因为多个请求在"同一时间段"并发执行
✓ 不是一个接一个排队执行
2️⃣ await asyncio.sleep(1.0) 的作用?
✓ 模拟异步I/O操作(数据库查询、网络请求等)
✓ 关键:它会"让出控制权",允许事件循环切换到其他协程
✓ 如果用 time.sleep(1.0),会阻塞整个线程,失去异步的意义
3️⃣ 为什么多出0.049秒和0.037秒?
✓ 网络往返延迟 (RTT)
✓ 线程/协程创建和调度开销
✓ 任务提交和结果收集的时间
✓ 这是正常的系统开销
════════════════════════════════════════════════════════════════════════════════
🎯 生活类比
想象一个餐厅:
串行服务(无并发):
服务员一次只服务1桌客人
客人1点餐→等待→上菜→吃完 (30分钟)
客人2点餐→等待→上菜→吃完 (30分钟)
...
10桌客人 = 300分钟 😫
并发服务(5个服务员):
5个服务员同时服务5桌
第一批5桌: 同时点餐、同时等待、同时上菜 (30分钟)
第二批5桌: 再服务 (30分钟)
10桌客人 = 60分钟 ✓
await asyncio.sleep 的意义:
厨师做菜时(I/O等待),服务员不是傻站着
而是去服务其他客人(切换到其他协程)
所以同一个服务员可以"同时"服务多桌
════════════════════════════════════════════════════════════════════════════════
📝 你的理解完全正确!
✅ await asyncio.sleep(1.0) 确实是模拟异步I/O操作
✅ 总耗时确实不是所有请求耗时的和(因为并发)
✅ 测试结果完美展示了并发的威力:
- 5个线程: 10秒 → 2秒 (5倍加速)
- 10个线程: 10秒 → 1秒 (10倍加速)
这就是为什么在处理大量网络请求、数据库操作时,并发编程如此重要!
════════════════════════════════════════════════════════════════════════════════

被折叠的 条评论
为什么被折叠?



