大家好,我是yes。
继上一篇说透I/O模型后,我们来谈谈网络 I/O 经常会伴随的几个容易令人混淆的概念:同步、异步、阻塞、非阻塞的区别。
这篇写完之后铺垫就差不多了,之后就正式开始 Netty 深度剖析了,敬请期待,嘻嘻。
话不多说,发车!
同步&异步
同步和异步指的是:当前线程是否需要等待方法调用执行完毕。
比如你调用一个搬运一百块石头的方法:
- 同步指的是调用这个方法,你的线程需要等待这一百块石头搬完,然后得到搬完了的结果,接着再继续执行剩下的代码逻辑。
//同步方式
result = 搬一百块石头();
//需等待搬完的结果,才能执行下面的逻辑
if(result) {
石头搬完了发工资();
}
计算下一次搬石头的任务();
- 异步指的是调用这个方法,立马就直接返回,不必等候这一百块石头还未搬完,可以立马执行后面的代码逻辑,然后利用回调或者事件通知的方式得到石头已经搬完的结果。
//异步方式
搬一百块石头({
//回调
石头搬完了发工资();
});
//不必等待石头搬完,立马执行下面的逻辑
计算下一次搬石头的任务();
可以很直观的看出,同步和异步就是调用方式的不同,这使得我们的编码方式也有所不同。
在异步调用下的代码逻辑相对而言不太直观,需要借助回调或事件通知,这在复杂逻辑下对编码能力的要求较高。而同步调用就是直来直去,等待执行完毕然后拿到结果紧接着执行下面的逻辑,对编码能力的要求较低,也更不容易出错。
所以你会发现有很多方法它是异步调用的方式,但是最终的使用还是异步转同步。
比如你向线程池提交一个任务,得到一个 future,此时是异步的,然后你在紧接着在代码里调用 future.get()
,那就变成等待这个任务执行完成,这就是所谓的异步转同步,像 Dubbo RPC 调用同步得到返回结果就是这样实现的。
阻塞&非阻塞
阻塞和非阻塞指的是:当前接口数据还未准备就绪时,线程是否被阻塞挂起。
何为阻塞挂起?就是当前线程还处于 CPU 时间片当中,调用了阻塞的方法,由于数据未准备就绪,则时间片还未到就让出 CPU。
所以阻塞和同步看起来都是等,但是本质上它们不一样,同步的时候可没有让出 CPU。
而非阻塞就是当前接口数据还未准备就绪时,线程不会被阻塞挂起,可以不断轮询请求接口,看看数据是否已经准备就绪。
至此我们可以得到一个结论:
- 同步&异步指:当数据还未处理完成时,代码的逻辑处理方式不同。
- 阻塞&非阻塞指:当数据还未处理完成时(未就绪),线程的状态。
所以同步&异步其实是处于框架这种高层次维度来看待的,而阻塞&非阻塞往往针对底层的系统调用方面来抉择,也就是说两者是从不同维度来考虑的。
再结合 I/O 来看
前提:程序和硬件之间隔了个操作系统,而为了安全考虑,Linux 系统分了:用户态和内核态
在这个前提下,我们再明确 I/O 操作有两个步骤:
- 发起 I/O 请求
- 实际 I/O 读写,即数据从内核缓存拷贝到用户空间
阻塞 I/O 和非阻塞 I/O。按照上文,其实指的就是用户线程是否被阻塞,这里指代的步骤1(发起I/O请求)。
- 阻塞 I/O,指用户线程发起 I/O 请求的时候,如果数据还未准备就绪(例如暂无网络数据接收),就会阻塞当前线程,让出 CPU。
- 非阻塞 I/O,指用户线程发起 I/O 请求的时候,如果数据还未准备就绪(例如暂无网络数据接收),也不会阻塞当前线程,可以继续执行后续的任务。
可以发现,这里的阻塞和非阻塞其实是指用户线程是否会被阻塞。
同步 I/O 和异步 I/O。按照上文,我们可以得知这就是根据 I/O 响应方式不同而划分的。
- 同步 I/O,指用户线程发起 I/O 请求的时候,数据是有的,那么将进行步骤2(实际 I/O 读写,即数据从内核缓存拷贝到用户空间),这个过程用户线程是要等待着拷贝完成。
- 异步 I/O,指用户线程发起 I/O 请求的时候,数据是有的,那么将进行步骤2(实际 I/O 读写,即数据从内核缓存拷贝到用户空间),拷贝的过程中不需要用户线程等待,用户线程可以去执行其它逻辑,等内核将数据从内核空间拷贝到用户空间后,用户线程会得到一个“通知”。
再仔细思考下,在 I/O 场景下同步和异步说的其实是内核的实现,因为拷贝的执行者是内核,一种是同步将数据拷贝到用户空间,用户线程是需要等着的。一个是通过异步的方式,用户线程不用等,在拷贝完之后,内核会调用指定的回调函数。
如果不理解上面,就只需记住:
- 同步I/O:指的是用户线程会需要等待步骤 2 执行完毕。
- 异步I/O:指的是用户线程不需要等待步骤 2 执行。
好了,如果以上的概念你都已经理解了的话,那么平日里我们所说的同步阻塞I/O,同步非阻塞I/O等其实就是把上面的两个步骤合起来看,应该不难理解。
我再简单的总结一下,关于 I/O 的阻塞、非阻塞、同步、异步:
- 阻塞和非阻塞指的是发起 I/O 请求后,用户线程状态的不同,阻塞I/O在数据未准备就绪的时候会阻塞当前用户线程,而非阻塞 I/O 会立马返回一个错误,不会阻塞当前用户线程。
- 同步和异步是指,内核的 I/O 拷贝实现,当数据准备就绪后,需要将内核空间的数据拷贝至用户空间,如果是同步 I/O 那么用户线程会等待拷贝的完成,而异步 I/O则这个拷贝过程用户线程该干嘛可以去干吗,当内核拷贝完毕之后会“通知”用户线程。
最后
要注意,不同场景下同一个名词意义可能不同。我这篇关于同步、异步、阻塞、非阻塞这几个概念是基于 I/O 场景下讲的。
个人能力有限,不知道有没有讲清楚,如有疑问可以留言区哈。
好了,关于 Netty 系列铺垫的差不多了,下篇就开始深入 Netty 之旅了!
关于前两篇铺垫的文章链接如下:
我是yes,从一点点到亿点点,我们下篇见!