创建时间: 2026-08-25最后更新: 2026-08-25

1. 同步

程序由一个个任务组成,例如读取文件、请求模型、查询数据库和计算结果。理解同步与异步,关键不是看代码有没有多个函数,而是看调用方在等待结果时能不能处理其他工作。

同步(synchronous)表示调用方发起一个操作后,要等这个操作完成并返回结果,才能继续执行下一步:

synchronous.py
01
import time
02
03
def fetch(name):
04
print(f"{name}:开始请求")
05
time.sleep(2)
06
print(f"{name}:请求完成")
07
return f"{name} 的结果"
08
09
first = fetch("任务一")
10
second = fetch("任务二")
11
12
print(first)
13
print(second)

执行过程是:

  1. 调用 fetch("任务一");
  2. 等待任务一完成;
  3. 得到 first;
  4. 调用 fetch("任务二");
  5. 等待任务二完成;
  6. 得到 second。

两个任务各等待 2 秒,总耗时大约是 4 秒。这种执行方式也叫串行:上一个任务没有结束,下一个任务就不会开始。

示例中的 time.sleep() 会阻塞当前线程。严格来说,「同步」描述的是调用方如何取得结果,「阻塞」描述的是等待期间当前线程是否还能执行其他工作。它们不是完全相同的概念,不过在这种常见的同步调用中,两者通常同时出现。

同步并不是落后的写法。任务之间存在先后依赖、程序规模很小,或者底层库只提供同步 API 时,同步代码通常更直接,也更容易调试。

2. 异步

异步(asynchronous)表示任务等待结果时可以暂停,把执行权交还给调度器。调度器可以利用这段等待时间运行其他已经就绪的任务,结果准备好后再恢复原来的任务。

Python 主要通过标准库 asyncio 提供这种能力:

asynchronous.py
01
import asyncio
02
03
async def fetch(name):
04
print(f"{name}:开始请求")
05
await asyncio.sleep(2)
06
print(f"{name}:请求完成")
07
return f"{name} 的结果"
08
09
async def main():
10
result = await fetch("任务一")
11
print(result)
12
13
asyncio.run(main())

这里出现了三个重要语法:

  • async def 定义协程函数;
  • await 等待异步操作,并允许当前协程暂停;
  • asyncio.run() 创建并管理事件循环,运行入口协程。

执行到 await asyncio.sleep(2) 时,fetch() 暂时不能继续,但它不会像 time.sleep(2) 那样占住事件循环。事件循环可以去运行其他已经准备好的任务。两秒后,fetch() 获得继续执行的资格,再从 await 后面恢复。

异步也不是「把函数扔到后台就不管了」。调用方最终仍然要通过 await 等方式取得结果、处理异常和取消任务。异步改变的是等待方式,不会改变业务上的依赖关系。

如果你熟悉 JavaScript,可以通过下面的表格建立初步对应关系:

JavaScriptPython asyncio作用与区别
Event Loopasyncio 事件循环负责调度异步任务和 IO 事件
async functionasync def定义异步函数;Python 调用后先得到协程对象,不会立即执行函数体
awaitawait暂停当前异步任务,等待结果完成后恢复
Promise协程、Task、Future都与未来结果有关,但职责不同,并不是一一对应关系
Promise.all()asyncio.gather()、TaskGroup并发等待一组异步任务
setTimeout()asyncio.sleep()延迟一段时间;sleep() 暂停协程,不接收回调函数
setTimeout(callback)loop.call_later()在指定延迟后调度普通回调
Promise.then()、queueMicrotask()没有直接对应的公开微任务 APIasyncio 使用自己的就绪队列、Task 和 Future 调度模型

这张表只用于帮助迁移已有认知,不能把两套运行时机械地画等号。尤其是 JavaScript 调用 async function 会返回 Promise,而 Python 调用 async def 函数只会创建协程对象;协程还需要被 await 或包装成 Task,才会真正开始执行。

JavaScript 通常用「宏任务、微任务」解释回调顺序,asyncio 则更适合用「协程、Task、Future、就绪队列、IO 事件和定时任务」理解。后面的「事件循环」文章会继续展开这些概念。

3. 并发

并发(concurrency)表示一段时间内有多个任务都在推进。它强调的是任务之间可以交替执行,而不是一定要在同一时刻运行。

只把函数写成 async def,不会自动产生并发。下面虽然使用了异步语法,两个任务仍然是串行的:

async-serial.py
01
import asyncio
02
import time
03
04
async def fetch(name):
05
print(f"{name}:开始")
06
await asyncio.sleep(2)
07
print(f"{name}:完成")
08
09
async def main():
10
started_at = time.perf_counter()
11
12
await fetch("任务一")
13
await fetch("任务二")
14
15
elapsed = time.perf_counter() - started_at
16
print(f"耗时约 {elapsed:.1f} 秒")
17
18
asyncio.run(main())

第一个 await fetch(...) 完成后,程序才会执行第二个调用,因此总耗时仍然接近 4 秒。await 表示等待,不表示自动同时启动后面的任务。

要形成并发,需要让多个协程在同一段时间内都进入事件循环。可以使用 asyncio.gather():

async-concurrent.py
01
import asyncio
02
import time
03
04
async def fetch(name):
05
print(f"{name}:开始")
06
await asyncio.sleep(2)
07
print(f"{name}:完成")
08
return name
09
10
async def main():
11
started_at = time.perf_counter()
12
13
results = await asyncio.gather(
14
fetch("任务一"),
15
fetch("任务二"),
16
)
17
18
elapsed = time.perf_counter() - started_at
19
print(results)
20
print(f"耗时约 {elapsed:.1f} 秒")
21
22
asyncio.run(main())

任务一执行到 await 后暂停,事件循环开始运行任务二;任务二也进入等待后,事件循环继续监听它们。大约两秒后,两个任务先后恢复并完成,所以总耗时接近 2 秒。

因此,「异步」和「并发」不是同义词:异步提供可暂停、可恢复的等待方式,并发则是利用这种能力,让多个任务在同一段时间内推进。

并发也不只属于 asyncio。多线程、多进程和分布式服务同样可以让多个任务在一段时间内推进;asyncio 只是 Python 处理大量 IO 等待时的一种高效并发方式。

4. 并行

并行(parallelism)表示多个任务在同一时刻真正执行,通常需要多个 CPU 核心、多个进程,或者能够并行工作的底层程序。

同步、异步、并发和并行描述的不是同一个维度:

概念关注的问题常见表现
同步调用方是否要立即等待结果得到结果后才继续下一步
异步等待期间能否让出执行权在 await 处暂停,稍后恢复
并发一段时间内是否有多个任务在推进多个任务交替执行或共同等待
并行同一时刻是否真的执行多个任务多个核心或进程同时计算

单线程事件循环可以实现并发,但不能让两段纯 Python 代码在同一个线程上并行执行:

concurrent-not-parallel.py
01
import asyncio
02
03
async def calculate():
04
total = 0
05
for number in range(20_000_000):
06
total += number
07
return total
08
09
async def main():
10
await asyncio.gather(calculate(), calculate())
11
12
asyncio.run(main())

calculate() 内部没有 await。第一个任务开始计算后不会主动让出执行权,第二个任务只能等待,因此使用 gather() 也不会使计算变快,反而可能阻塞整个事件循环。

真正需要并行执行 CPU 密集型任务时,通常会使用多个进程、任务队列或独立的计算服务。线程是否能够提升 CPU 计算效率,还会受到 Python 实现和 GIL 等因素影响,后面的「线程」文章会专门说明。

5. 场景

asyncio 的优势来自「等待期间还有其他工作可以做」,因此最适合 IO 密集型任务:

  • 请求大模型或其他 HTTP 服务;
  • 查询数据库、Redis 和向量数据库;
  • 等待消息队列;
  • 读取网络数据流;
  • 同时维护大量连接。

CPU 密集型任务则会持续使用处理器,例如:

  • 大量数值计算;
  • 图片和视频处理;
  • 本地模型推理;
  • 大规模文本解析和压缩。

可以用下面的判断方式选择执行模型:

任务常见选择
简单流程或有严格先后依赖同步或异步串行
大量相互独立的 IO 等待asyncio 并发
无法替换的同步阻塞 IO线程池或 asyncio.to_thread()
CPU 密集型计算多进程、任务队列或计算服务

异步并不一定比同步更快。只有当程序存在可重叠的等待时间时,并发执行才能提高整体吞吐量。单个异步请求的耗时通常不会凭空缩短,改进的是等待期间处理其他请求的能力。

6. 服务

在 FastAPI 服务中,服务器已经创建并维护事件循环:

fastapi-async.py
01
import asyncio
02
from fastapi import FastAPI
03
04
app = FastAPI()
05
06
async def call_model():
07
await asyncio.sleep(1) # 模拟等待模型服务
08
return "模型回复"
09
10
@app.get("/chat")
11
async def chat():
12
result = await call_model()
13
return {"result": result}

当 call_model() 等待模型响应时,同一个 worker 可以继续处理其他请求。这里不会因为 100 个请求就默认创建 100 个线程,多个请求通常表现为由事件循环调度的多个协程。

不过,只有底层调用本身支持异步,这种写法才有意义。如果在 async def 接口中调用同步 HTTP 客户端或执行耗时计算,它仍然会阻塞事件循环。

还要避免在 FastAPI 接口中调用 asyncio.run()。服务器已经有正在运行的事件循环,接口只需要使用 await。LangChain 的 ainvoke()、astream() 等异步方法也可以按照这种方式接入。

正在验证登录状态
请稍候,验证完成后将继续显示文章内容