同步、异步、阻塞、非阻塞怎么理解?

#计算机网络#操作系统#面试八股

同步、异步、阻塞、非阻塞怎么理解?

这是两个互相独立的维度,很多人把它们混成一维,于是"同步=阻塞"的错误就出来了:

  • 同步/异步:说的是结果由谁完成、调用何时返回。同步是调用方自己等结果;异步是发起后立刻返回,完成时由内核/框架通知。
  • 阻塞/非阻塞:说的是等待时线程的状态。阻塞是线程被挂起让出 CPU;非阻塞是立刻返回一个"还没好"的错误码。

组合起来有四种,其中"异步阻塞"只是把等待放在通知上,本质仍是异步。

1. 四象限

阻塞非阻塞
同步read() 没数据就一直睡,数据到了唤醒并拷贝read() 没数据立刻返回 EAGAIN,自己轮询
异步提交后阻塞等完成事件(io_uring_wait_cqe、aio_suspend)提交后立刻返回,完成回调/事件里取结果(IOCP、io_uring)
同步阻塞:  [发起]──等待数据──[拷贝]──返回     线程全程被占用
同步非阻塞:[发起]✗ [发起]✗ [发起]✓──[拷贝]──返回   线程空转,靠轮询
多路复用:  [注册N个fd]──select等待任一就绪──[逐个read]   同步非阻塞的批量版
异步:      [提交]──返回──(内核自己等、自己拷贝)──[完成事件]   两阶段都不用管

2. 用烧水理解

  • 同步阻塞:站在水壶前盯着,水不开就不走。
  • 同步非阻塞:每 10 秒来看一眼,没开就回去干活(轮询有开销,间隔大了延迟高)。
  • IO 多路复用:一次盯一排壶,哪个响处理哪个(select/poll/epoll)。
  • 异步:装个鸣笛壶,水开了它自己叫,你全程不用管,回来水已经在杯子里(数据已拷到用户缓冲区)。

关键差别在"数据拷贝"这一阶段归谁:多路复用只告诉你"可读了",read 还得自己调,所以它是同步的; 真正的异步 IO 连拷贝都由内核完成后才通知你。

3. 代码对比

/* 同步阻塞 */
n = read(fd, buf, sizeof buf);            // 没数据就睡,直到有数据

/* 同步非阻塞 + 轮询 */
int fl = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, fl | O_NONBLOCK);
while ((n = read(fd, buf, sizeof buf)) < 0 && errno == EAGAIN) {
    /* 干点别的,或者 usleep(1000) 再试 */
}
# 多路复用:一个线程盯多个 fd,仍是同步 IO
import selectors, socket
sel = selectors.DefaultSelector()
srv = socket.socket(); srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", 9000)); srv.listen(); srv.setblocking(False)
sel.register(srv, selectors.EVENT_READ, data=None)

while True:
    for key, _ in sel.select(timeout=1):
        if key.data is None:
            conn, _ = srv.accept(); conn.setblocking(False)
            sel.register(conn, selectors.EVENT_READ, data=b"")
        else:
            chunk = key.fileobj.recv(4096)      # 就绪后自己拷贝,所以是同步
            if not chunk:
                sel.unregister(key.fileobj); key.fileobj.close()
            else:
                key.data += chunk

4. 常见追问

Q:异步一定比同步快吗? 不一定。异步提升的是并发能力和资源利用率,单次延迟可能更高(多一次事件通知、队列出入)。 连接数少、逻辑简单时,同步阻塞反而更省事也更快。

Q:为什么说 Java NIO 是同步非阻塞,而 Node.js 是异步? Java NIO 的 Selector 只通知"可读",channel.read() 仍由业务线程完成拷贝,属于多路复用; Node.js 把 IO 交给 libuv 线程池/内核,回调触发时数据已就绪,业务代码感知不到等待过程。

Q:协程算异步吗? 协程是编程模型层面的挂起恢复,底层仍靠 epoll 之类的多路复用。用同步写法获得异步的并发效果, 这也是 Go、Python asyncio、Kotlin 协程流行的重要原因。

小结

记住两句话:同步/异步看谁来完成并通知结果,阻塞/非阻塞看调用返回前线程是否被挂起。 epoll 是"同步非阻塞的批量等待",io_uring/IOCP 才是"真异步"。面试时先把这个二维表画出来, 再补一句"多路复用省的是等待阶段,拷贝阶段还得自己来",基本不会被追问倒。

笔记加载中…