服务器 和 客户端 :异步+线程池并发

一、服务器

"""
简单的演示服务器 - 用于并发测试
模拟一个有延迟的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倍加速)

这就是为什么在处理大量网络请求、数据库操作时,并发编程如此重要!

════════════════════════════════════════════════════════════════════════════════

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值