同步(阻塞)

调用一个操作,等待它完成后才继续执行。

response = http_get(url)   # 等待网络返回
print(response)            # 拿到结果才继续

优点:逻辑直观,顺序即执行顺序,易于理解和调试。

缺点:等待期间,线程被"占着"但不能干活。

为什么同步会慢

一个 Web 服务器处理请求时,大量时间在等待 I/O:

  • 数据库查询;
  • 调用外部 API;
  • 读写磁盘。

这些操作 CPU 几乎不参与,只是等待。

如果每个请求都要独占一个线程等待,那么 1000 个并发请求就需要 1000 个线程——线程的内存开销(每个线程有独立的栈,通常 MB 级)和上下文切换开销会很可观。

异步(非阻塞)

发起一个操作后不等待,继续做别的事;操作完成后通过回调/通知得到结果。

核心机制:

一、事件循环(Event Loop)

一个线程不断轮询:有哪些 I/O 操作完成了?完成了就处理对应的回调。

二、I/O 多路复用

操作系统提供的机制:select、poll、epoll(Linux)、kqueue(BSD/macOS)、IOCP(Windows)。

它们允许一个线程同时监视大量文件描述符(socket),当其中任何一个就绪时得到通知。

这是异步 I/O 的技术基础——不是"多线程同时等待",而是"一个线程监视多个连接"。

三种写法

一、回调(Callback)

http.get(url, (err, response) => {
  // 完成后执行
});

问题:多层嵌套导致回调地狱(Callback Hell)——代码层层缩进,难以阅读和错误处理。

二、Promise / Future

fetch(url)
  .then(r => r.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

改进:链式调用替代嵌套,错误处理统一。

三、async / await

const data = await fetch(url).then(r => r.json());

效果:写法像同步代码,但底层是异步的。

这是目前最主流的写法——兼顾了可读性和性能。

注意:await 只是语法糖。它不阻塞线程,而是让出控制权给事件循环,等 Promise 完成后再恢复执行。

什么时候用异步

适合:I/O 密集型任务(网络请求、数据库访问、文件读写)。

不适合:CPU 密集型任务。

原因:异步模型是单线程(或少量线程)处理大量 I/O。如果任务是纯计算,异步没有收益——因为 CPU 一直在忙,没有等待可以"省"下来。

CPU 密集任务应该用:多进程、多线程、或原生的并行机制(如 Python 的 multiprocessing,或把计算交给专门的服务)。

一个经典陷阱

在异步代码中调用阻塞操作。

比如在 Node.js 的事件循环中执行一个同步的大计算,或者调用一个同步的文件读取——这会阻塞整个事件循环,导致所有其它请求都卡住。

原则:异步代码里,所有 I/O 都必须用异步版本。

并发 vs 并行

  • 并发(Concurrency):同时处理多个任务(不一定同时执行,如事件循环交替处理)。
  • 并行(Parallelism):同时执行多个任务(需要多核)。

异步 I/O 提供的是并发,不是并行。

说明:本文为编程模型的知识整理。