📄articles 📋snippets 📁categories ⚙️uses ✏️about 🔍search

关于IO操作的操作的同步、异步、阻塞、非阻塞

IO 操作的同步/异步、阻塞/非阻塞——这四个词经常被混为一谈。面试被问到“同步和阻塞有什么区别”时,很多人支支吾吾。其实它们描述的是不同层面的事情:同步/异步关注的是调用方和被调用方的关系,阻塞/非阻塞关注的是线程自身的状态。本文用一个 IO 读取的两阶段模型把它们彻底拆开。

一、IO 操作的两个阶段

一次完整的 IO 操作(比如读取磁盘文件)分为两步:

  1. 数据准备阶段:内核从磁盘/网络把数据读到内核缓冲区。这一步的快慢取决于硬件
  2. 数据复制阶段:内核把数据从内核空间复制到用户空间(进程缓冲区)。这一步由 CPU 执行

所有的“阻塞”“非阻塞”“同步”“异步”描述的都是程序在这两个阶段中的行为。

二、阻塞 IO vs 非阻塞 IO

这对概念描述的是第一阶段(数据准备)时线程的状态:

  • 阻塞 IO:发起 IO 请求后,线程挂起等待,直到数据准备完成才返回。比如 C 语言的 read() 默认就是阻塞的——没数据就等着
  • 非阻塞 IO:发起 IO 请求后立刻返回。如果数据没准备好,返回一个错误码(如 EAGAIN),程序可以先去干别的事,过会儿再试

关键区别:阻塞/非阻塞只看“发起 IO 请求时线程是否被挂起”。它是线程内的状态变化。

三、同步 IO vs 异步 IO

这对概念描述的是调用方和被调用方之间的协作模式

  • 同步 IO:调用方发起 IO 请求后,必须主动等待或轮询,直到 IO 完成才能继续。阻塞 IO、非阻塞 IO、IO 多路复用(select/poll/epoll)、信号驱动 IO 都属于同步 IO——因为第二阶段的数据复制需要调用方自己参与
  • 异步 IO:调用方发起 IO 请求后立即返回,不等待。当内核完成全部两个阶段后,通过回调或信号通知调用方。整个过程调用方完全不参与数据复制

Linux 的原生异步 IO(AIO)直到现在对磁盘文件的支持都不完善,大部分场景下“异步”是通过线程池模拟的。

四、四种组合:实际场景

组合 调用方行为 被调用方行为 典型例子
同步阻塞 发请求→等着 没结果就等着 传统的 read()
同步非阻塞 发请求→等着 没结果立即返回,好了通知 IO 多路复用 + epoll
异步阻塞 发请求→不等 没结果就等着,好了通知 少见,无实用价值
异步非阻塞 发请求→不等 没结果立即返回,好了通知 Windows IOCP、Node.js

五、一张图总结


                ┌─ 阻塞 IO:read() 卡住等数据
同步 IO ────────┤
                └─ 非阻塞 IO:轮询 + read()

异步 IO:aio_read(),内核搞定一切后通知你

阻塞/非阻塞 = 线程自己的状态(第一阶段是否挂起)
同步/异步   = 调用方与被调用方的关系(第二阶段谁来做)

记住这个公式就够了:同步异步看数据复制谁负责,阻塞非阻塞看线程是否被挂起。

相关阅读:进程、线程、协程的区别

© 2021 关于IO操作的操作的同步、异步、阻塞、非阻塞 · 本文由 Charlie 原创撰写,发布于 sodebug.com。 未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。
GK
独立开发者,在 WordPress、Nginx 和各种 API 之间切换。不写没用的东西。

related相关文章

#01Unity 3D使用HighlightingSystem插件实现游戏物体高亮/闪烁1 min#02Awake、Start、Update、FixedUpdate、LateUpdate执行顺序1 min#03如何理解进程、线程、协程1 min
$ echo "less bullshit, more debugging" | send-to-inbox
新文章直接发到邮箱,一个月 2-4 封。
$ subscribe →