就初学者而言,尤其是初次接触fastAPI这一以异步为核心的框架的哥们儿来说,可能根本就搞不懂,进程、线程、协程的层级关系。写代码时混用同步阻塞逻辑,导致接口并发卡顿、事件循环卡死。

今天我们就来结合业务场景深入探讨一下三者的适用场景、底层调度、优缺点。


一、进程、线程、携程分别是什么?

1.进程

进程是程序在操作系统中的一次执行实例,拥有完全独立的资源:独立内存空间、文件句柄、CPU 时间片、网络端口、堆内存。它拥有很好的隔离性,多进程之间互不干涉,某一进程的崩溃不会拉着其他进程一起陪葬。
举个例子:本地同时运行python main.py两次,就是两个独立进程;Docker 容器本质是一组隔离的进程。

2.线程

一个进程一个进程中至少会有一个线程,多个线程将共享这一进程所分配到的全部系统资源,如内存、全局变量等。线程统一由系统调度。在python中受全局解释器锁(GIL锁)的限制,只适合用于I/O密集型阻塞场景(如:sleep休眠、网络请求、文件读写等),IO 阻塞时线程会释放 GIL,其他线程可抢占锁执行,实现并发提速

3.协程

它存在于单个线程内部,完全受代码控制,不由操作系统干涉,执行权由await关键字操纵,I/O阻塞时可释放线程,单个线程内能实现上万并发,也是fastAPI高性能主要依仗。

层级关系:操作系统 → 进程(资源隔离) → 线程(内核调度) → 协程(用户态调度) 1 个进程可以创建多个线程;1 个线程内部可同时运行成千上万个协程。

二、三者分别解决什么问题?

1. 多进程:解决 CPU 密集计算、故障隔离问题

痛点:Python GIL 锁限制,单进程单线程无法利用多核 CPU;服务崩溃需要环境隔离。

场景:大数据计算、AI 推理、多服务独立部署(Docker)。

缺陷:创建、销毁、上下文切换开销极大,资源占用高。

2. 多线程:简化单进程内多 IO 任务,降低切换成本

痛点:进程切换太重,轻量 IO 任务不需要全新进程;

场景:少量同步 IO 脚本、老旧同步接口、文件读写;

缺陷:上万并发线程会造成操作系统内核调度压力,吞吐量暴跌;同步阻塞 IO 会卡死整个线程。

 

3.协程 (async/await):解决海量 IO 高并发场景(Web 后端首选)

痛点:线程并发上限低、切换有内核开销;同步阻塞 IO 会占用线程无法处理其他请求。

优势:

  1. 用户态切换,无系统调用,切换成本几乎可以忽略;
  2. IO 等待时主动释放线程,单线程同时处理上万 HTTP 请求;
  3. 内存占用极低,单个协程仅几十 KB 栈内存;
  4. 完美适配 FastAPI、aiokafka、aioredis、异步 SQLAlchemy 全套异步生态。

三、调度者

进程:操作系统内核全权创建、调度、销毁;开发通过multiprocessing、Docker 操作;

线程:操作系统内核调度,进程内资源共享;开发通过threading模块使用;

协程:完全由用户程序调度(Python 内置asyncio事件循环),操作系统无感知;后端开发写 FastAPI 异步路由、异步客户端时使用async def / await

四、实战

1.多进程(CPU密集型场景)

from multiprocessing import Process

def cpu_task():
    # 模拟CPU密集计算
    count = 0
    for i in range(10**7):
        count += 1
    print(f"计算完成,结果:{count}")

if __name__ == "__main__":
    # 开启两个独立进程,利用多核CPU
    p1 = Process(target=cpu_task)
    p2 = Process(target=cpu_task)
    p1.start()
    p2.start()
    p1.join()
    p2.join()

特点:两个进程内存完全隔离,不受 GIL 锁限制,真正并行计算。
 

2.多线程(同步I/O阻塞)

import threading
import time

def sync_io_task():
    # time.sleep同步阻塞,占用整个线程
    time.sleep(2)
    print("同步IO任务执行完成")

if __name__ == "__main__":
    t1 = threading.Thread(target=sync_io_task)
    t2 = threading.Thread(target=sync_io_task)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

缺陷:time.sleep会阻塞线程,并发上万时操作系统调度卡顿;不能用于高并发 Web 服务。

3.协程 async/await(FastAPI标准写法)

import asyncio
from fastapi import FastAPI

app = FastAPI()

# 定义协程函数,模拟异步IO(DB/Redis/Kafka)
async def async_io():
    # await主动让出事件循环,不阻塞服务
    await asyncio.sleep(2)
    return {"data": "异步IO返回数据"}

@app.get("/async_demo")
async def route_demo():
    # 并发执行两个IO任务,总耗时≈2s,而非4s
    task1 = async_io()
    task2 = async_io()
    res1, res2 = await asyncio.gather(task1, task2)
    return {"task1": res1, "task2": res2}

执行流程拆解:

  • 接口请求进入协程函数,执行到await时,当前协程主动暂停;
  • 事件循环收回线程控制权,去处理其他用户的 HTTP 请求;
  • 2 秒 IO 等待结束,循环切回当前协程,执行后续代码;
  • 全程仅占用 1 个主线程,无内核切换,支持十万级 IO 并发。

常见问题:

1.协程中不能使用同步的阻塞方法:time.sleep()requestspymysql会卡死整个事件循环,必须替换为asyncio.sleephttpx.AsyncClient、异步数据库驱动

2.await只能使用在async def中普通同步def函数、线程内不能使用await

3.CPU密集型场景不要用协程,必须开多进程规避GIL锁

4.忘记写await:直接接收协程对象,I/O逻辑不会等待,业务逻辑会失效

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐