同步(阻塞)
调用一个操作,等待它完成后才继续执行。
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 提供的是并发,不是并行。
说明:本文为编程模型的知识整理。