当前位置: 首页 > news >正文

阻塞、非阻塞、同步、异步的区别是什么?

同步异步描述的是被调用方。阻塞非阻塞描述的是调用方。二者没有必然联系。

  1. 阻塞是调用方A发出命令后,必须等待B返回结果。非阻塞是调用方A发出命令后,A不需要等待B,可以做自己的事情。
  2. 同步是B收到A的指令之后会立即执行,A可以得到结果。异步是B收到A的指令之后不会立即执行要做的事情,A的本次调用不会得到结果,但是B执行完要做的事情之后会通知A。

我们常常混淆的同步异步、阻塞非阻塞其实是放在特定场景下的,不能一概而论,IO也分为磁盘IO和网络IO。这里所讲的IO一般指网络IO。

  • 阻塞IO和非阻塞IO:指的是socket编程中发起read函数系统调用读取数据后是否阻塞住
    • 如果一直等待到有数据才返回,这个read就是阻塞的,也是同步的
    • 如果没有数据就返回-1而不是等待,这个read就是非阻塞的,也是同步的
  • 同步IO和异步IO:指的操作系统内核是否自动将数据从内核空间拷贝到用户空间
    • 如果需要read函数自己将数据拷贝到用户空间就是同步IO
    • 如果内核自动将数据拷贝到用户空间,并且通知用户,就是异步IO(一般在Linux上用的少,windows有完整实现)
  • 同步执行和异步执行:指的是是否按照代码编写的顺序执行
    • 如果按照代码位置顺序执行,就是同步执行
    • 如果像js里面的setTimeout,设置了callback之后,执行时候直接执行后面的。到达设定时间才开始执行前面设定的callback函数。就是异步执行。
  • 同步调用和异步调用:指的是是否通过多线程调用函数
    • 如果只在单线程内顺序执行方法就是同步调用
    • 如果新建线程执行方法就是异步调用
  • 同步请求和异步请求:指的是前端发起的是整体页面请求还是局部请求
    • 如果是整个网页的请求那就是同步请求
    • 如果是ajax这种局部请求,那就是异步请求,一般会定义回调函数用于接收响应后的处理。

网络IO演进分析

同步阻塞网络IO

最开始学习基础Linux的Socket编程时候,流程如下:

// 伪代码
// 创建 Socket 的时候,可以指定网络层使用的是 IPv4 还是 IPv6,传输层使用的是 TCP 还是 UDP。
listenSocketFd = socket(); 
// 给这个 Socket 绑定一个 IP 地址和端口
bind(listenSocketFd);       
// 监听
listen(listenSocketFd);      
while(1) {// 阻塞,等待TCP 全连接队列不为空,内核态拿出对应socket返回用户态connSokcetFd = accept(listenSocketFd);fds.add(connSokcetFd);for (fd : fds) {// 阻塞,等待数据就绪,拷贝到用户空间的 bufint n = read(fd, buf);  // 使用数据做事情doThing(buf);}
}

客户端和服务端具体的交互流程如下,其中服务端的accept 函数和read 函数都会阻塞,前者等待三次握手结束才会向下执行,后者等待数据就绪才会向下执行(参考小林coding):

1653358023380-bd2b2ec3-5aa3-4292-ae88-e3875752bc76.png

这里主要关注read函数,看下图可以知道会一直阻塞直到数据从网卡拷贝到内核缓冲区完成。read才开始执行将数据从内核空间拷贝到用户空间(参考低并发编程):

1653363269431-9aea911f-360e-40ec-a468-79eec00b0636.png

整个同步阻塞IO的流程如下:

acc34503d0470cf022de238071507067.svg

上面同步阻塞IO会导致客户端一直阻塞,新客户端连接也不能处理。一种解决办法就是新建一个线程来处理客户端的请求:

while(1) {// 阻塞,等待TCP 全连接队列不为空,内核态拿出对应socket返回用户态connSokcetFd = accept(listenSocketFd);// 新建线程处理客户端请求pthread_create(doWork); 
}
void doWork() {// 阻塞,等待数据就绪,拷贝到用户空间的 bufint n = read(connSokcetFd, buf);  // 使用数据做事情doThing(buf);  // 关闭连接,循环等待下一个连接close(connSokcetFd);  
}

扩展知识:进程和socket在内核中的关系:

1653359534685-9ec4b22a-7cb0-4cb7-ac4c-6f96753575d9.png

同步非阻塞网络IO

使用多线程可以避免一个人阻塞其他人,但是效率还是比较低。在发起系统调用read的时候,依旧是阻塞的。而且多线程在用户多时会导致大量线程创建,不是明智之举。所以需要在操作系统层面提供支持,也就是提供非阻塞的 read 函数:

1653363364824-6a3a735b-c613-467f-b582-1ddfa8f5dfce.png

实现的时候就将客户端创建的连接都放到一个集合里面,然后对集合里面的连接不断执行read直到读取到数据:

// 伪代码
while(1) {// 阻塞,等待TCP 全连接队列不为空,内核态拿出对应socket返回用户态connSokcetFd = accept(listenSocketFd);fds.add(connSokcetFd);for (fd : fds) {// 阻塞,等待数据就绪,拷贝到用户空间的 bufint n = read(fd, buf);  if (n != -1) {// 使用数据做事情doThing(buf);}}
}

整个同步非阻塞IO的流程如下:

88f0839c2c2ee2eb91d3b40d3829e934.svg

总结

同步阻塞和非阻塞都是按照写的代码的顺序执行的,只是后者立即返回错误结果,需要代码层面不断轮询直到拿到结果。

但是不断轮询当前所有的连接是否有数据就绪也不是好的办法,因为每一次轮询都是一次系统调用,开销还是比较大的,所以操作系统实现了 IO多路复用 这一利器避免轮询。我是deltaqin,下篇介绍IO多路复用!

http://www.hskmm.com/?act=detail&tid=29539

相关文章:

  • 如何防范员工泄露数据给 AI?2025年选型与落地实战版
  • Linux文本编辑三剑客之grep
  • Linux文本编辑三剑客之sed
  • 做了项目经理才发现:上台发言,其实都有套路
  • 占位符
  • 什么是IO多路复用?
  • 进程、线程和协程之间的区别和联系
  • 挣点小钱的副业(附带新手教程)0元的快乐
  • Linux文本编辑三剑客之awk
  • 软考~高效的系统规划与管理师考试—知识篇—V2.0—第四章 IT 服务规划设计 — 2017 年 2018 年 2020 年 2022 年 2023 年
  • 10.12
  • 从“优化工具”到“价值生态”:多价值主体系统如何重塑AI价值对齐范式
  • 2.2 深度学习(Deep Learning)
  • 结对项目
  • 第十二篇
  • 2.1 函数逼近(Function Approximation)
  • Elasticsearch 备份:snapshot 镜像使用篇
  • 本次科研收获
  • 2025.10.12 - 20243867孙堃2405
  • git clone 克隆下载深度层级仓库最新源码而不是整个仓库
  • 九、可供选择的体系结构
  • Linux查看一个文件的时候发生了什么?
  • 内存管理
  • 2025 年 10 月金属门窗厂家加盟代理品牌推荐排行榜,行业权威盘点与品质红榜发布
  • 五、指令集架构深入分析
  • ARC 208 Div.2
  • 八、系统软件
  • 七、输入输出和存储系统
  • 那快把题端上来吧(五)
  • 机器学习学术研讨会柏林举办