接触前后端一段时间了,我对异步这个常用概念仍然没有一个非常好的认识,因此写了这篇文章来进行学习.

异步概念总览

异步这一概念是相对于同步来说的,我们先来看看同步的定义:

In computer science , synchronization is the coordination of simultaneous threads or processes to complete a task with correct runtime order and no unexpected race conditions; see synchronization (computer science) for details.

也就是说,同步就是顺序执行了,那么异步就是不按顺序执行,那到底是怎么执行的呢?

非常奇怪的是,你很难找到一个文档去真正的讲清楚异步是什么,我翻阅了各大问答网站,wiki,博客,都很难找到一个真正详尽的解答,这反过来说明这个概念并非如想象中的那般简单.

让我们看看AI怎么说(尽管我用的是免费模型):

Gemini说

从物理机制和执行模型来看,异步(Asynchronous) 的本质是:允许调用方在发起一个耗时操作后,不等待结果返回,直接继续执行后续任务。

为了透彻理解,我们需要从“阻塞”与“让渡”的核心逻辑出发:

1. 核心模型:同步 vs. 异步

  • 同步 (Synchronous):线性执行。CPU 发起 I/O 请求(如读文件、连数据库)后,必须原地“挂起”等待硬件返回数据。此时 CPU 虽然空闲,但无法处理其他任务,这就是阻塞
  • 异步 (Asynchronous):非线性执行。CPU 发起 I/O 请求后,立即标记该任务为“等待中”,然后转身去处理其他代码。当硬件数据准备好时,通过事件通知(回调、信号或 Promise)告知 CPU 回来处理结果。

2. 生活化类比:餐厅模式

  • 同步模式(排队取餐):你在柜台点完餐,必须站在收银台前等着厨师做。厨师没做好,你不能走,后面的人也点不了餐。你的时间被彻底阻塞。
  • 异步模式(取餐铃):你点完餐,服务员给你一个“取餐铃”(对应编程中的 FuturePromise)。你可以回座位玩手机或看书。当铃声响了(对应回调通知),你才过去取餐。你没有被“阻塞”在柜台。

3. 底层物理基础:为什么需要异步?

计算机硬件的速度存在极大的数量级差异,这是异步存在的根本原因:

操作 消耗 CPU 指令周期(近似) 等效人类时间
CPU 寄存器操作 1 周期 1 秒
内存访问 200 周期 3 分钟
SSD 硬盘读取 2,000,000 周期 23 天
网络请求 (跨海) 200,000,000 周期 6 年

结论:如果采用同步模式,CPU 在等待一次网络请求时,相当于浪费了人类时间里的“6 年”。异步通过让渡控制权,让 CPU 在这“6 年”里去处理成千上万个其他任务。


4. 常见的实现机制

异步在不同领域有不同的物理表现:

  • 多线程 (Multi-threading):开一个新的员工(线程)去等,主员工继续干活。缺点是切换员工(上下文切换)很累,且费工资(内存占用高)。
  • 事件循环 (Event Loop):如 JavaScript 或 Python 的 asyncio。只有一个员工,但他手里有个待办列表。遇到耗时任务就往列表一扔,做完一个就看一眼列表有没有回信。
  • 回调函数 (Callback):“做完了叫我”。
  • Promise / Await:“给我一个承诺,好了我再继续”。

5. 关键误区纠偏

  1. 异步 ≠ 并行
    • 并行是“多个人同时搬砖”。
    • 异步是“一个人在等砖拉过来的同时,去拌水泥”。
  2. 异步不一定更快
    • 对于 计算密集型(纯算数学题),异步反而慢,因为多了调度开销。
    • 对于 I/O 密集型(等网络、等文件),异步能极大地提高系统的并发能力(吞吐量)

概念辨析

AI对异步的概念定义如下:
允许调用方在发起一个耗时操作后,不等待结果返回,直接继续执行后续任务。

从这个角度来看,我们平常所说的多线程并不是异步,多线程是将同一个任务拆分成了很多小任务去执行,但异步并不存在将任务分开执行这个概念,而是存在一个顺序上的先后划分.

因此,我们可以这样解释异步:

将耗时的,或者需要其他操作来返回结果的任务挂起,优先执行其他任务,等到需要执行这个任务时再执行.

以python爬虫为例:

通过 asyncio 库和 await 关键字,程序在发起网络请求后不再原地阻塞,而是去执行其他请求,直到数据返回再回调处理。这种模式在单线程下实现了高并发,极大地提升了 I/O 密集型任务的吞吐量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import asyncio
import aiohttp

async def fetch(session, url):
# 发送请求并挂起,等待期间 CPU 可以处理其他任务
async with session.get(url) as response:
return await response.text()

async def main():
urls = ["https://example.com" for _ in range(10)]
# 使用 aiohttp 管理异步连接池
async with aiohttp.ClientSession() as session:
# 并发创建所有爬取任务
tasks = [fetch(session, url) for url in urls]
# 统一等待所有任务完成
pages = await asyncio.gather(*tasks)
print(f"成功爬取 {len(pages)} 个页面")

# 启动事件循环
if __name__ == "__main__":
asyncio.run(main())