理解同步、异步、阻塞、非阻塞

一、核心区别:同步 vs. 异步

这两者的区别关键在于 消息通知机制 和 程序执行流

特性同步 (Synchronous)异步 (Asynchronous)
核心思想顺序执行。调用者发起调用后,必须等待这个调用返回结果,才能继续执行后续代码。并行执行。调用者发起调用后,无需等待结果,立刻继续执行后续代码。被调用方通过某种方式(如回调、通知)主动告知调用者结果。
消息通知调用者主动等待结果返回。调用者被动接收通知(回调函数被自动调用)。
执行流线性、阻塞的。代码顺序就是执行顺序。非线性、跳跃的。现在发起的调用,未来的某个时间点才处理结果。
性能容易导致调用者阻塞,资源利用率低调用者不会阻塞,可以处理其他任务,资源利用率高
编程模型简单、直观,符合人类思维习惯。相对复杂,通常需要回调函数、事件循环、Promise/async-await等机制。
比喻打电话:你打电话问朋友问题,一直拿着电话听筒等待他回答,期间什么也干不了。发微信:你发微信问朋友问题,然后就可以去忙别的事。朋友回复后,微信通知你,你再去处理他的回复。

二、另一个维度:阻塞 vs. 非阻塞

这两者的区别关键在于 调用者在等待结果时的状态

特性阻塞 (Blocking)非阻塞 (Non-blocking)
核心思想调用结果返回前,调用者所在的线程会被挂起,无法执行任何其他操作。调用结果返回前,调用者所在的线程可以继续处理其他任务
线程状态线程被操作系统置于 休眠(sleeping) 状态,不占用CPU。线程保持 运行(running) 状态,可以继续占用CPU执行命令。
关注点关注的是线程自身的状态(是继续运行还是被挂起)。

一个重要结论:同步 != 阻塞,异步 != 非阻塞。 它们是描述不同方面的术语。


三、四种组合方式的深度解析

现在我们将“消息通知机制”和“调用者状态”这两个维度组合起来,就得到了四种I/O模型。理解这个组合是面试中的重中之重。

为了更好地理解这四种模式,我们可以通过一个“客户端查询数据库”的例子,并结合其程序执行流程来对比:

场景:客户端调用 queryDB() 函数从数据库获取数据

1. 同步阻塞 (Synchronous Blocking)

  • 含义:调用者发起一个同步调用,并且在调用结果返回前,线程被挂起(阻塞)。

  • 例子:Java中的 Socket.getInputStream().read()。调用时,线程会一直阻塞,直到网络上有数据到来。

  • 评价最简单的模型,但性能最差。每个线程只能处理一个连接,大量连接需要大量线程,上下文切换开销巨大。

2. 同步非阻塞 (Synchronous Non-blocking)

  • 含义:调用者发起一个同步调用,但调用立即返回一个状态值(而非结果)。调用者需要不断地主动轮询(polling)去检查结果是否就绪。在轮询间隙,线程可以做其他事(非阻塞)。

  • 例子NIO 中的 SocketChannel.configureBlocking(false)。调用 read 方法时,如果无数据,会立刻返回 0(或其他状态码),而不是阻塞。程序员需要自己写循环去不断尝试读取。

  • 评价:避免了线程阻塞,但轮询会消耗大量CPU资源,效率很低。

3. 异步阻塞 (Asynchronous Blocking)

  • 含义:调用者发起一个异步调用,并提供回调函数。但调用者在调用后,自己却阻塞在一个等待通知的对象上,而不是去处理其他任务。

  • 例子selectpollepoll 这些I/O多路复用技术。程序员发起一个 select 调用,这个调用会阻塞,直到其监视的多个Socket连接中有一个或多个完成了I/O操作(变为就绪状态)。当 select 返回后,程序员再自己去同步地读取那些就绪的Socket。异步地得知了哪个操作已完成,但读取操作本身是同步的。

  • 评价:这是高性能网络编程的基石。单个线程可以管理成千上万个网络连接,极大地提升了资源利用率。虽然调用者线程在 select 处是阻塞的,但它是在等待所有连接的通知,而不是某一个。

4. 异步非阻塞 (Asynchronous Non-blocking)

  • 含义:调用者发起一个异步调用,并提供回调函数。调用完成后,调用者立刻返回去做其他事情。当操作真正完成时,由操作系统(或底层运行时)自动调用事先提供的回调函数来处理结果。调用者既不需要等待,也不需要轮询。

  • 例子:Linux下的 AIO(真正的异步IO),Windows下的 IOCP,以及高级语言中的 Promiseasync/await(如JavaScript、C#、现代C++、Python)。Node.js 的整个设计就构建在此模型之上。

  • 评价理想和最高效的模型。它将“得知就绪”和“数据读写”两个阶段都异步化了,完全释放了调用者线程。


四、同步/异步 vs. 阻塞/非阻塞的联系

  • 同步/异步 和 阻塞/非阻塞 是描述I/O行为的两个不同维度

    • 同步/异步 关注的是 消息通信机制

    • 阻塞/非阻塞 关注的是 程序在等待结果时的状态

  • 它们可以自由组合,但常见的、有实际意义的组合就是上面四种。

  • “异步阻塞” 这个组合看似矛盾,但在I/O多路复用(select/epoll)中确实存在,并且极其有用。


五、常见问题总结

Q:“讲讲同步、异步、阻塞、非阻塞的区别,以及它们的组合。”

A:

“这些概念可以从两个维度来理解:

第一是消息通知机制

  • 同步:调用者需要主动等待主动轮询结果。

  • 异步:调用者发起调用后,被动等待被调用方通知(如回调函数)。

第二是调用者等待时的状态

  • 阻塞:调用结果返回前,调用者线程被操作系统挂起,无法执行其他操作。

  • 非阻塞:调用结果返回前,调用者线程可以继续执行其他操作。

它们的常见组合有四种:

  1. 同步阻塞:最简单也最低效。线程发起调用后就被挂起,空等结果。

  2. 同步非阻塞:线程不断轮询检查状态,CPU空转严重,效率不高。

  3. 异步阻塞:这是I/O多路复用模型(如select/epoll)。线程阻塞在等待多个IO事件上,任何一个就绪后就同步地去处理。它是高性能网络编程的基础。

  4. 异步非阻塞:最理想的模型。如AIO或async/await。线程发起调用后立即返回做别的事,操作完成后由系统自动触发回调处理,效率和资源利用率最高。

现在让我们把这一切串起来。为了更直观地理解这四种I/O模型的核心区别,特别是“谁在执行”以及“线程状态如何”,可以参考下面的流程图:

如何理解这张图:

  1. 第一层抉择(同步 vs. 异步):这决定了任务的执行主体是谁。是应用程序线程亲自执行(同步),还是委托给操作系统内核执行(异步)。

  2. 第二层抉择(阻塞 vs. 非阻塞):这决定了线程在等待结果时的状态。是挂起休眠(阻塞),还是继续执行其他任务(非阻塞)。

  3. 异步阻塞:这是一个特殊且有效的组合。线程虽然委托了任务,但自己却选择休眠,直到被委托的任务完成通知唤醒。这正是 select/epoll 模型的工作方式。

  4. 异步非阻塞:这是最理想的模式,线程既委托了任务,又不会空等,资源利用率最高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xzkyd outpaper

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值