异步
并发模型对比
| 框架 | 并发方式 | 原理 | 适合场景 |
| Flask | 多线程/多进程 | 每个请求新建线程 | CPU密集型、阻塞IO |
| FastAPI | 异步事件循环(单线程) | await 切换任务 | 高并发IO操作 |
flask实现并发大概流程:
假如app.py集成了路由,然后路由下定义了业务逻辑函数,那么当多个请求到来时,各个请求互不影响,app.py会为各个请求创建线程,每个线程各自执行。所以假如业务逻辑函数实现需要3秒,当三个请求同时来到时,总处理时间也是3秒。
对于高并发以及实时场景,更主流的框架是faskAPI,后面简称fast。所以我重点学习fast
下面是app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from fastapi import FastAPI import asyncio
app = FastAPI()
@app.get('/sleep') async def sleep(): print("任务开始") await asyncio.sleep(3) print("任务结束") return 'ok'
if __name__ == '__main__': import uvicorn //要用uvicorn启动fast项目,但是官方推荐在命令行用 //uvicorn app:app --reload 启动 uvicorn.run(app, host="127.0.0.1", port=8000)
|
下面是test_fastapi_client.py,用于模拟用户请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import asyncio import time import httpx//支持异步的客户端,与asyncio.gather配合来同时发请求
URL = "http://127.0.0.1:8000/sleep"
async def fetch(client, request_id): """异步发起 GET 请求并打印耗时""" start_time = time.time() response = await client.get(URL) end_time = time.time()
print(f"[请求 {request_id}] 状态码: {response.status_code}, " f"响应内容: {response.text}, " f"耗时: {end_time - start_time:.2f}秒")
async def main(): async with httpx.AsyncClient() as client: tasks = [] total_start = time.time()
for i in range(1, 4): tasks.append(fetch(client, i))
await asyncio.gather(*tasks)
total_end = time.time() print("-" * 40) print(f"总耗时: {total_end - total_start:.2f}秒")
if __name__ == "__main__": asyncio.run(main())
|
运行后终端打印分别如下:


为什么是2,1,3呢?因为逻辑是先创建任务(把任务加到人物列表中),然后并发执行,执行先后顺序多少会有细微不同(随机情况)
为什么要用httpx.AsyncClient 保持长连接
这里涉及到通信问题。首先要说明flask和fast都是用的http协议通信。
在使用普通的同步 HTTP 客户端(如 requests)时,每次发起请求都会经历:建立 TCP 连接 -> 发送数据 -> 接收数据 -> 关闭连接。如果并发发 3 个请求,就会重复这个过程 3 次。
而在 FastAPI 的异步世界中,我们使用 httpx.AsyncClient 作为上下文管理器(async with httpx.AsyncClient() as client:),起到连接复用的功能:它在后台维持底层的 TCP 连接不中断。当连续发出多个请求时,它会直接复用已经建立好的通道,省去了反复握手、建立连接的开销。
fast这个异步模型,基于事件循环来工作。当遇到await操作时,当前协程会主动让出事件循环的控制权,使其立马去处理其它的http请求,等await操作完成了再回来继续执行下一步。这里可以理解为主厨同时炒多个菜,然后到处走动
注意点:
虽然这里例子中fast用的是http协议,但是它也原生支持websocket协议,用于实时通话等场景:
1 2 3 4 5 6
| @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() while True: data = await websocket.receive_text() await websocket.send_text(f"Message text was: {data}")
|
fast有个优点就是可以自动生成接口文档,假如项目运行在localhost:9999,那么localhost:9999/doc就是文档界面。
TCP协议
这就是著名的三次握手、四次挥手协议
我们可以把它想象成两个人(客户端 A 和服务器 B)在嘈杂的环境中打电话:
- 第一次握手(A -> B): A 拨通电话说:“喂,你能听到我说话吗?”(这在技术上叫发送
SYN 报文,表示请求建立连接)。 - 第二次握手(B -> A): B 听到了,回复道:“我能听到你说话!那你能听到我说话吗?”(技术上叫返回
SYN + ACK,表示同意并反问确认)。 - 第三次握手(A -> B): A 回答道:“我也能听到你说话!”(技术上叫发送
ACK,表示最终确认)。
既然有开始,就有结束。当数据传输完毕,不需要再聊的时候,就需要断开连接。因为 TCP 是双向的(既能发又能收),所以关闭连接比建立连接多了一步,被称为四次挥手:
- A 说:“我说完了。”(FIN)
- B 回:“好的,我知道了。”(ACK)
- B 接着说:“我也说完了。”(FIN)
- A 回:“好的,那就挂了吧。”(ACK)
协议套娃
TCP协议是HTTP协议和Websocket协议的底层协议。这里联系到协议套娃:
在计算机里,当你通过浏览器访问一个网页时,数据是这样一层层往下传的:
- 应用层 (HTTP): 浏览器说:“我要请求
/sleep 接口!”(写好信件内容) - 传输层 (TCP): 操作系统接过信件,把它塞进信封,贴上收发件人地址,并准备发起“三次握手”确认通道。(安排物流运输)
- 网络层 (IP): 邮政局看着信封上的地址,决定这封信要经过哪几个城市的转运中心才能到达对方电脑。(规划跨城市运输路线)
- 物理/链路层 (网线/Wi-Fi): 最终变成电信号或无线电波,通过网线和路由器发送出去。(货车开上高速公路)
当数据到达服务器时,再反过来一层层拆开(解封装),最后把 HTTP 的内容交给 FastAPI 或 Flask 处理。
ORM(对象关系映射)
对于fast,官方给的orm是sqlalchemy(之前有过学习),今天学习Tortoise,受Djiango启发。
fast的ORM操作,主要参考
1、创建模型
以选课系统为例:创建个模型类文件 models.py,每个类对应一张表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
from tortoise.models import Model from tortoise import fields
class Clas(Model): name = fields.CharField(max_length=255, description='班级名称')
class Teacher(Model): id = fields.IntField(pk=True) name = fields.CharField(max_length=255, description='姓名') tno = fields.IntField(description='账号') pwd = fields.CharField(max_length=255, description='密码')
class Course(Model): id = fields.IntField(pk=True) name = fields.CharField(max_length=255, description='课程名') teacher = fields.ForeignKeyField('models.Teacher', related_name='courses', description='课程讲师')
class Student(Model): id = fields.IntField(pk=True) sno = fields.IntField(description='学号') pwd = fields.CharField(max_length=255, description='密码') name = fields.CharField(max_length=255, description='姓名') clas = fields.ForeignKeyField('models.Clas', related_name='students') courses = fields.ManyToManyField('models.Course', related_name='students',description='学生选课表')
|
2、创建数据库连接配置文件
创建settings.py 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| TORTOISE_ORM = { 'connections': { 'default': { 'engine': 'tortoise.backends.mysql', 'credentials': { 'host': '10.10.0.52', 'port': '3306', 'user': 'root', 'password': 'Jingxxxxxxxx', 'database': 'fastapi', 'minsize': 1, 'maxsize': 5, 'charset': 'utf8mb4', "echo": True } }, }, 'apps': { 'models': {
'models': ['models'], 'default_connection': 'default',
} }, 'use_tz': False, 'timezone': 'Asia/Shanghai' }
|
3、启动项目
app.py启动项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from fastapi import FastAPI import uvicorn from tortoise.contrib.fastapi import register_tortoise from settings import TORTOISE_ORM
app = FastAPI()
register_tortoise( app, config=TORTOISE_ORM, )
if __name__ == '__main__': uvicorn.run("quickstart:app", port=8080, reload=True, workers=1)
|
正常启动后便连接到了数据库
4、根据模型类创建数据库表
这里依靠ORM迁移工具aerich,结合tortoise异步orm框架使用。
迁移工具可以理解为核心功能是生成和执行数据库迁移脚本(SQL),实现数据库的增删改(没有查,并非少打字)功能,然后异步可以理解为aerich和tortoise是高度绑定的,它们共用配置文件,当执行查询等操作时,这些I/O操作都是非阻塞的。
回到操作步骤,这一步骤分多个小步骤,大概是初始化配置、然后迁移操作、回退操作(类似git)等,都是用命令实现,命令语句和大多工具的用法差不多。具体可参考原文。
查询功能
在分发的路由中的路由函数中,把sql的查询语句写成查询函数:
all查询:

循环打印:

静态页面
就是把查询到的信息,以你喜欢的方式,更好看的呈现在浏览器上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
接口: @api_student.get("/index") async def show_student(request:Request): students = await Student.all() return templates.TemplateResponse( 'index.html', { 'request': request, 'students':students
}, )
|

启动程序后,浏览器访问:

本作品由 Hafsun 于 2026-06-10 19:24:11 发布
除特别声明外,本站作品均采用
CC BY-NC-SA 4.0 许可协议,转载请注明来自
欢迎回家