【系列文章】Linux系统中的IO模型[01篇]----同步、异步、阻塞、非阻塞到底有什么区别?

Linux系统IO模型解析:同步与异步、阻塞与非阻塞区别

【系列文章】Linux系统中的IO模型[01篇]----同步、异步、阻塞、非阻塞到底有什么区别?

该文章为系列文章:Linux系统中的IO模型中的第一篇
该系列的导航页连接:
【系列文章】Linux系统中的IO模型-导航页



前言

在计算机编程,尤其是网络编程和并发编程领域,“同步 / 异步” 和 “阻塞 / 非阻塞” 是四个高频出现但极易混淆的概念。很多开发者会将 “同步” 等同于 “阻塞”,“异步” 等同于 “非阻塞”,但实际上这是两组完全不同维度的概念 —— 前者描述的是 “任务间的协作方式”,后者描述的是“进程 / 线程等待资源时的状态”

本文将从概念定义、核心区别、实际场景示例三个层面,彻底讲清这四个概念的本质,帮你摆脱 “概念混淆” 的困扰。

一、先明确两组概念的核心定义

要区分这四个概念,首先要把 “同步 / 异步” 和 “阻塞 / 非阻塞” 拆分成两个独立维度理解,再看它们的组合场景。

1. 第一维度:同步(Synchronous) vs 异步(Asynchronous)

(关注任务完成的通知方式)
“同步” 和 “异步” 的核心是 “任务的发起者是否需要等待任务完成”,或者说 “任务结果的通知方式”。
也就是说:
同步: 调用者发出请求后,必须自己想办法确认操作完成。你得自己“等”或者“查”,内核/对方不会主动告诉你。
举个生活例子: 你去咖啡店点单,点完后站在柜台前等待,直到店员把咖啡递给你,你才离开柜台去座位 —— 这里 “你” 是任务发起者,“做咖啡” 是任务,你需要等待任务完成(拿到咖啡)才能进行下一步(去座位)。

异步: 调用者发出请求后,不用等,系统会在操作完成时主动通知你(信号、回调、事件)。你可以去干别的事,完成时会有人“叫你”。
生活例子: 你去咖啡店点单,点完后店员给你一个取餐码,你可以先去座位上玩手机(执行后续操作);当咖啡做好后,店员喊取餐码(通知机制),你再去取咖啡 —— 这里你不需要等待任务完成,任务完成后会主动通知你。

2. 第二维度:阻塞(Blocking) vs 非阻塞(Non-blocking)

(关注调用时的等待行为)
“阻塞” 和 “非阻塞” 的核心是 “进程 / 线程在等待资源(如 IO、锁、数据)时的状态”—— 等待期间是否能做其他事。
阻塞: 当进程 / 线程请求某个资源(如读取文件、网络请求)时,如果资源未就绪,进程 / 线程会进入暂停状态(阻塞态),放弃 CPU 使用权,直到资源就绪后才被唤醒继续执行。也就是说调用函数时,如果条件不满足(比如 read() 时没有数据),就会停在那儿等,直到有结果再返回。
生活例子: 你去银行办业务,排队时只能站在队伍里等待(不能做其他事),直到轮到你才能去窗口办理 ——“排队” 就是阻塞状态,无法利用等待时间做其他事。

非阻塞: 当进程 / 线程请求某个资源时,如果资源未就绪,不会进入暂停状态,而是立即返回 “资源未就绪” 的结果,进程 / 线程可以继续执行其他任务;之后会通过 “轮询” 或 “通知” 的方式检查资源是否就绪。也就是说调用函数时,如果条件不满足,立刻返回一个错误码(通常是 EAGAIN),不会等。
生活例子: 你去银行办业务,取号后发现前面人很多,你可以去银行旁边的便利店买瓶水(做其他任务),每隔几分钟回来看一下叫号屏(轮询),直到叫到你的号再去办理 —— 等待期间可以做其他事,不会被 “卡住”。

二、关键区别:两组概念的本质差异

很多人混淆这四个概念,本质是没分清它们的 “讨论维度”。下表清晰对比了两组概念的核心差异:

对比维度同步/异步 (Synchronous/Asynchronous)阻塞/非阻塞(Blocking/Non-blocking)
核心关注点任务间的协作方式 (结果如何通知)等待资源时的进程/线程状态
讨论主体任务发起者与任务的关系进程/线程本身
关键行为发起者是否等待任务完成等待资源时是否放弃CPU
典型场景函数调用 (同步:等待返回;异步:回调)IO 操作 (阻塞:等IO 完成;非阻塞:轮询)
1.举个编程领域的经典例子 —— 网络请求(如 HTTP 调用):

假设我们需要调用一个远程接口获取数据,然后处理数据。我们从 “同步 / 异步” 和 “阻塞 / 非阻塞” 两个维度拆解可能的场景:

场景 1:同步阻塞(最常见的 “传统模式”)
过程:
    主线程发起网络请求(调用接口);
    由于网络请求需要时间(资源未就绪),主线程进入阻塞状态,放弃 CPU,什么都不做;
    等待网络请求完成(数据返回),主线程被唤醒;
    主线程处理返回的数据。
特点:简单但低效,主线程在等待 IO 时完全闲置,无法处理其他任务(如用户交互)。
典型场景:Java 中未使用线程池的HttpURLConnection同步调用。
场景 2:同步非阻塞
过程:
    主线程发起网络请求,由于资源未就绪,立即返回 “请求未完成” 的结果(非阻塞);
    主线程不等待,转而处理其他任务(如处理本地缓存、响应其他请求);
    主线程每隔一段时间 “轮询” 检查网络请求是否完成(如每隔 100ms 调用一次检查接口);
    当轮询发现请求完成后,主线程处理返回的数据(同步:发起者主动获取结果)。
特点:主线程不阻塞,能利用等待时间做其他事,但 “轮询” 会消耗 CPU 资源(频繁检查)。
典型场景:Linux 下的select/poll IO 模型(早期非阻塞 IO 的实现方式)。
场景 3:异步阻塞(较少见,易被误解)
过程:
    主线程发起异步网络请求(注册回调函数),然后调用一个 “阻塞等待” 的接口;
    主线程进入阻塞状态,等待 “请求完成” 的通知;
    网络请求在后台线程完成后,通过信号唤醒主线程;
    主线程调用回调函数处理数据。
特点:任务是异步的(后台执行),但发起者(主线程)仍在阻塞等待结果 —— 本质是 “异步任务 + 阻塞等待通知”。
典型场景:Java 中Future.get()方法(通过ExecutorService提交异步任务后,调用get()会阻塞直到任务完成)。
场景 4:异步非阻塞(高性能并发的 “黄金模式”)
过程:
    主线程发起异步网络请求(注册回调函数),立即返回(非阻塞);
    主线程继续处理其他任务(如接收新的请求、处理本地逻辑);
    网络请求在后台完成后,通过 “事件循环” 触发回调函数;
    主线程在空闲时执行回调函数,处理返回的数据。
特点:完全不阻塞,主线程利用率最高,没有轮询开销 —— 是高并发场景(如 Web 服务器、消息队列)的首选模式。
典型场景:Node.js 的事件循环、Java 的 Netty(NIO)、Linux 的epoll IO 模型。

三、常见误区澄清

在实际开发中,很多开发者会有以下误区,这里逐一澄清:

误区 1: “同步就是阻塞,异步就是非阻塞”
错误原因: 混淆了 “任务协作方式” 和 “线程状态” 两个维度。
反例: 异步阻塞(如Future.get())—— 任务是异步执行的,但发起者会阻塞等待结果;同步非阻塞(如轮询检查 IO)—— 任务是同步获取结果的,但线程不会阻塞。

误区 2:“非阻塞一定比阻塞高效”
错误原因: 忽略了 “轮询开销”。
实际情况:

如果 IO 等待时间很短(如读取内存中的小文件),阻塞模式更高效(无需轮询,减少 CPU 消耗);
如果 IO 等待时间很长(如跨地域网络请求),非阻塞模式更高效(线程可以利用等待时间做其他事)。

误区 3:“异步非阻塞只能用在 Node.js 这类单线程模型”
错误原因:将 “异步非阻塞” 与 “单线程” 绑定。
实际情况:异步非阻塞是一种 “IO 模型”,与线程数量无关。例如:

Java 的 Netty(多线程)使用 NIO 实现异步非阻塞;
Go 的 Goroutine(轻量级线程)通过 “M:N 调度” 实现异步非阻塞 IO。

四、总结

要彻底分清 “同步 / 异步” 和 “阻塞 / 非阻塞”,记住以下核心结论:

先分维度:
聊 “同步 / 异步” 时,想 “任务结果怎么通知”(发起者等不等);
聊 “阻塞 / 非阻塞” 时,想 “等待资源时线程干不干其他事”(放不放弃 CPU)。
看实际场景:
简单业务(如本地计算):同步阻塞足够用,实现简单;
高并发 IO 场景(如 Web 服务器、消息中间件):优先选异步非阻塞,最大化线程利用率;
中等并发场景(如普通后端接口):同步非阻塞(如线程池 + 轮询)或异步阻塞(如Future)是折中方案。
避免绝对化:
没有 “绝对更好” 的模式,只有 “更适合场景” 的模式 —— 根据 IO 等待时间、并发量、开发复杂度选择合适的模型,才是最优解。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值