字节二面-关于异步同步阻塞非阻塞的关系


字节二面的时候被问到这个问题,当时因为没有准备根本答不上来。后来闲下来仔细推敲,发现这是一个很难三言两语解释清楚的一块知识点,放在校招面试里我觉得过于八股而有点恶心人。

首先,异步同步和阻塞非阻塞是两个完全不同的概念,要讨论这个问题我想先把背景放在java线程和网络io这块可能说的会比较明白。

异步同步是用于描述消息通信机制的,而阻塞非阻塞是用于描述线程状态的。

同步的情况下我可以选择阻塞也可以非阻塞,阻塞即线程挂起等待返回结果就绪继续运行,非阻塞则线程轮询返回结果是否就绪,这里的差别在于是否让出CPU。吞吐量上来说,因为选择了同步并没有太大的区别,但是根据系统的CPU和IO的资源和响应瓶颈,在CPU频繁切换上下文的情况下耗时可能会有所差别。

异步则一般都是非阻塞,但异步不全是回调式。

如果你选择调用以后继续执行业务逻辑,直到必须关心返回结果的时候才调用get函数,这种异步就是将来式。这种情况下你就得分调用get的时候是否返回结果已经就绪。如果返回已经就绪,那么自然是好的,系统的吞吐量可以获得提升,否则的话从调用get开始异步又变为了同步,你需要选择阻塞还是非阻塞等待结果就绪(通常都是轮询,这会占用CPU)。

对于回调式来说,则需要另外编写一个回调函数,返回结果后回调函数会另起一个线程对返回结果进行处理,结果就是主线程在执行完阻塞等待子线程对返回结果处理完毕。对单机吞吐量来说,这两者也没有太大的区别。

再回到面试中,现在面试言必谈分布式RPC,对RPC来说将来式和回调式当然是有区别的。在不确定返回时间的情况下,将来式会不可避免的占用线程池的线程数量,如果下游接口不可用则会导致大量的返回超时。

更为可取的另一种做法是,在调用时不要求返回结果,请求方另起一个接口用于回调以完成调用后的业务,这样RPC的调用结果就会以参数的形式返回给调用方。需要注意的是务必要确保被调方接收到了请求(超时重试),并且对于整个微服务的调用链路要做好链路追踪,考虑好总体的超时时间,及时降级。