在 IT 圈混饭吃, 不管你用什么编程语言, 从事前端还是后端, 阻塞, 非阻塞, 异步, 同步这些概念, 都需要清晰地掌握, 否则, 怎么与面试官谈笑风生(chui niu pi)? 但是, 掌握这些概念又不是非常容易, 尤其对非科班出身的, 更加困难. 本文试图给出一个清晰简明但不失深刻的介绍, 希望对大家有所帮助.
1, 从 I/O 说起
这些概念之所以容易令人迷惑, 在于很多人对 I/O 就没有清晰准确的理解, 后面的理解自然不可能正确. 我想用一个具体的例子来说明一下 I/O.
设想自己是一个进程, 就叫小进吧. 小进需要接收一个输入, 我们不管这个输入是从网络套接字来, 还是键盘, 鼠标来, 输入的来源可以千千万万. 但是, 都必须由内核来帮小进完成, 为啥内核这么霸道? 因为计算机上运行的可不只是咱小进一个进程, 还有很多进程. 这些进程兄弟也可能需要从这些输入设备接收输入, 没有内核居中协调, 岂不是乱套.
从小进的角度看, 内核帮助它完成输入, 其实包括三个步骤:
1, 内核替小进接收好数据, 这些数据暂时存在内核的内存空间
2, 内核将数据从自己的内存空间复制到小进的内存空间
3, 告诉小进, 输入数据来了, 赶快读吧
这三步看似挺简单, 其实在具体实现时, 有很多地方需要考虑:
0, 小进如何告诉内核自己要接收一个输入?
1, 内核接到小进的请求, 替小进接收好数据这段时间, 小进咋办?
2, 内核在将数据复制到小进的内存空间这段时间, 小进咋办?
3, 到底什么时候告诉小进数据准备好了, 是在内核接收好数据之后就告诉小进, 还是在将数据复制到小进的内存空间之后再告诉他?
4, 内核以什么样的方式告诉小进, 数据准备好了?
2, 阻塞式 I/O 模型
对上面 5 个问题, 最简单的解决方案就是阻塞式 I/O 模型, 它的过程是这样的:
小进: 内核内核, 我要接收一个键盘输入, 快点帮我完成!
内核: 好咧! biubiu! 一个阻塞丢给小进, 小进顿时石化, 就像被孙悟空点了定一样.
就这样, 小进在石化中, 时间一点点流逝. 终于, 内核收到了数据.
内核: 数据终于来了, 我要开干了! duang duang duang, 先把数据存在自己的内核空间, 然后又复制到小进的用户空间.
内核: biubiu! 一个解除阻塞丢给小进, 小进瞬间复活, 小进的记忆还是停留在让内核帮他接收输入时.
小进: 哇! 内核真靠谱, 数据已经有了! 干活去!
我们可以看到, 小进发出接收输入的请求给内核开始, 就处于阻塞状态, 直到内核将数据复制到小进的用户空间, 小进才解除阻塞.
3, 非阻塞式 I/O
小进发现, 阻塞式 I/O 中, 自己总要被阻塞好久, 好不爽啊, 于是小进改用了非阻塞式 I/O, 其过程是这样的:
小进: 内核内核, 我要接收一个输入, 赶紧帮我看看, 数据到了没有, 先说好, 不要阻塞我.
内核: 查看了一下自己的内核空间, 没有发现数据, 于是迅速告诉小进, 没有呢! 并继续帮小进等着数据.
如此这样, 小进不断地问内核, 终于, 过了一段时间, 小进再一次询问时, 内核往自己的空间中一查, 呦! 数据来了, 不胜其烦的内核迅速告诉小进, 数据好了!
小进: 快给我!
内核: biu! 一个阻塞丢给小进, 悲催的小进还是石化了!
内核赶紧将自己空间的输入数据复制到小进的用户空间, 复制好后.
内核: biu! 一个非阻塞丢给小进, 小进立马复活
小进: 哇! 数据来了, 啥也不说, 干活!
我们看到, 所谓的非阻塞 I/O, 其实在内核将数据从内核空间复制到小进的用户空间时, 小进还是被阻塞的.
4, 信号驱动式 I/O
非阻塞 I/O 中, 小进不停地问内核, 数据好了没有啊, 内核感觉太烦了, 于是想出一个好办法.
内核告诉小进, 本内核升级了, 如果想要我替你接收输入, 请先注册一个信号处理函数, 等数据准备好时, 我会发信号给你. 于是, 现在的流程是这样的:
小进: 注册信号处理函数, 告诉内核, 自己要接收一个输入, 然后继续干活!
内核: 收到函数, 开始执行数据接收
接收完成时, 给小进发送信号, 信号处理函数收到信号, 开始向内核发送读数据请求
内核: biu! 阻塞了小进, 并把数据从内核空间复制到小进的用户空间.
内核: biu! 解除了阻塞
小进: 哇! 数据来了! 啥也不说, 干活去!
5, 异步 I/O
上面的三种 I/O 解决方案中, 小进都被阻塞了, 只不过是阻塞时间长短不一样, 第一种方案中小进被阻塞的时间长一些, 在内核接收数据以及将数据复制到小进的用户空间时, 都被阻塞.
第二, 第三种方案中, 只在内核将数据从内核空间复制到小进的用户空间时, 小进才被阻塞.
我们现在说的异步 I/O, 目的就是让小进绝对不被阻塞. 其过程是这样的:
小进: 内核内核, 我要接收一个输入, 弄好了告诉我. 同时将一个信号和信号处理函数告诉内核, 然后继续干自己的活了.
内核: 得了您嘞, 您先忙.
一直到内核接收到数据并将数据从内核空间复制到小进的用户空间后, 内核才给小进发送信号. 小进在信号处理函数中可以直接处理数据.
6, 那啥是同步呢?
一句话, 凡是让小进阻塞 (不管长短) 的 I/O 方案都是同步 I/O. 也就是说, 阻塞, 非阻塞, 信号驱动式都是同步 I/O.
7, 无总结, 不进步
上面, 我们从完成输入时, 进程与内核的交互方式的角度分析了不同的 I/O 解决方案, 在这个过程中, 解释清楚了阻塞, 非阻塞, 同步, 异步的概念.
作者: milter
链接:
https://www.jianshu.com/p/3d603166f54d
来源: https://juejin.im/entry/5bfccdf2518825773a2ecaa0