Python 的异步编程和协程是现代 Python 高并发网络编程(如 FastAPI、asyncio、AI Agent 框架)的核心。

很多习惯了 C++ 或 Java 等传统语言的开发者,刚接触 Python 异步时容易把它和“多线程”混淆。我们可以从底层原理、设计哲学、语法进化以及与多线程/C++ 的对比四个维度来彻底讲透。

一、 核心概念:什么是“协程”?

1. 协程 vs 线程 vs 进程

  • 进程(Process):操作系统分配资源的最小单位,拥有独立内存空间。切换开销极高。

  • 线程(Thread):操作系统调度的最小单位,共享进程内存。由 OS 内核强制抢占式调度(Preemptive Scheduling),存在内核态/用户态切换开销,多线程共享数据需要加锁(Mutex)。

  • 协程(Coroutine)用户态的轻量级“线程”。它不需要操作系统内核参与调度,而是由程序员/运行时在代码层面协作式调度(Cooperative Scheduling)

2. 协作式调度(关键特性)

  • 线程调度:OS 随时可能在某行代码中间“挂起”当前线程,去跑另一个线程(抢占)。

  • 协程调度:协程必须主动交出 CPU 控制权(在 Python 中通过 await 关键字),事件循环才会去安排执行其他协程。如果一个协程死循环不交出控制权,整个线程都会被卡死。

二、 为什么 Python 这么依赖协程?(底层动力:GIL)

在 C++ 中,如果需要处理高并发网络 IO,常见的做法是 “线程池 + epoll (Reactor 模式)”

但在 Python(CPython 解释器)中,存在 GIL(Global Interpreter Lock,全局解释器锁)

  • 无论你有多少个 CPU 核心,同一个 Python 进程在任意时刻只能有一个线程在执行 Python 字节码

  • 多线程的尴尬:在 Python 中用多线程处理 CPU 密集型任务,性能不仅不会提升,还会因为频繁的线程上下文切换和 GIL 争抢而变慢。

  • 协程的优势:既然多线程无法利用多核,且带年来锁竞争与内核切换开销,那么在单线程内用协程来搞定大量网络 IO 就是成本最低、吞吐最高的方案!

三、 异步编程的核心“铁三角”

理解 Python 异步编程,只需要搞懂三个核心概念:

[ 事件循环 Event Loop ]  <--- 调度管理
         │
         ├─── [ 协程 Task / Coroutine ] (遇到 await 主动 yield 控制权)
         ├─── [ 协程 Task / Coroutine ]
         └─── [ 协程 Task / Coroutine ]

1. 事件循环 (Event Loop)

  • 它是整个异步系统的心脏(底层基于 Linux 的 epoll 或 Windows 的 IOCP)。

  • 它是一个无限循环,负责监控所有注册在它上面的 Socket/任务。当某个 Socket 可读/可写时,唤醒对应的协程继续向下执行。

2. 协程函数 (Coroutine Function)

  • async def 定义的函数。

  • 调用 async def 函数不会立刻执行代码,而是返回一个“协程对象”。

3. await 关键字

  • 只能在 async def 函数内部使用。

  • await expr 的真正含义:“我在这里需要等待一个异步操作(如网络请求)完成,请事件循环把 CPU 控制权拿走去跑别的协程;等这个操作好了,再回来接着跑我后面的代码。”

也就是python始终都是单线程串行执行的?

答案是:在开启了 asyncio 协程的单进程中,代码确实始终运行在同一个线程上,并且在任意微观时刻(CPU 时间片内)只有一段代码在执行;但宏观上,它是通过“非阻塞交替”实现了“并发(Concurrency)”,而不是传统意义上死板的“同步串行”。

另外,如果放眼整个 Python 语言生态,Python 并不是只能写单线程,它完全支持多线程和多进程。

我们可以从以下三个层次来精准理解:

1. 微观视角:是的,单线程单 Core,同一时间只做一件事

asyncio 的模型下:

  • 整个程序只有一个主线程(Single Thread)。

  • 没有并行(Parallelism):如果你有两个 CPU 核心,asyncio 无法同时利用这两个核心去跑两行 Python 代码。

  • 只要你的代码在做 CPU 计算(例如 x = 1 + 1 或者 JSON 解析),Python 就会绝对串行地逐行执行,期间没有任何其他代码能插进来。

2. 宏观视角:它不是“同步等待”的串行,而是“非阻塞交替”的并发

传统“同步串行”和协程“异步并发”的最大区别,在于遇到 IO 阻塞(比如网络等待)时 CPU 在干什么

传统的“同步串行”
线程:[发请求A] ───(死等 1 秒 API 返回)───> [收到A] ──> [发请求B] ───(死等 1 秒 API 返回)───> [收到B]
总耗时:2 秒

CPU 有 99% 的时间都在白白干等网络数据包返回。

协程的“非阻塞并发”
线程:[发请求A] ──(遇到 await,交出控制权)──> [发请求B] ──(等待事件循环唤醒)──> [收到A/B]
总耗时:1 秒

当代码执行到 await reader.readline()await http_client.get() 时:

  1. 当前协程主动暂停,告诉事件循环:“我去等网络数据了,你先拿 CPU 去干别的。”

  2. 事件循环立刻切到另一个协程继续跑。

  3. 当网络数据包到达时,操作系统通知事件循环,事件循环再唤醒刚才暂停的协程恢复执行。

3. 放眼整个 Python 语言:Python 并不只有单线程

经常有人说“Python 是单线程的”,这其实是一个误区。你需要区分以下三个层面的概念:

机制 线程/进程数 适用场景 核心特点
asyncio 协程 单线程 超高并发网络 IO(如 AI Agent、Web API) 极其轻量,用户态调度,无锁开销,但无法利用多核 CPU。
threading 多线程 多线程 中等 IO 密集型(如多图下载、串口读取) 受限于 GIL 锁,无法实现 CPU 算力的并行,但能做到多线程阻塞 IO 的并发。
multiprocessing 多进程 多进程 CPU 密集型(如图像处理、矩阵计算、深度学习) 每个进程有独立的 Python 解释器和独立 GIL,能够实现真正的多核 CPU 并行计算!
Logo

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

更多推荐