浅谈阻塞/非阻塞、同步/异步——从linux read()系统调用出发

浅谈阻塞/非阻塞、同步/异步

–从linux IO系统调用出发

阻塞与非阻塞主要从进程/线程的角度出发:

  • 阻塞(blocking):教科书年年考的概念——调用方(主线程)发起调用之后挂起直到被调用方法返回。阻塞通信仍然有很多应用场景,典型例子如如spring框架下传统的Spring MVC的Servlet Stack;(如下图)
  • 非阻塞(non-blocking):相对阻塞而言,调用方发起调用之后,不会挂起,而是马上继续执行调用块之后的逻辑。

浅谈阻塞/非阻塞、同步/异步——从linux read()系统调用出发_第1张图片

而同步与异步主要从通信的角度出发:

  • 同步(synchronous):同步机制是指发送方发送请求后,需要等待接收到接收方发回的响应后,才接着发送下一个请求。所以,发送方和接收方对请求的处理步调是一致的;
  • 异步(asynchronous):发送方发出一个请求后,不等待接收方响应这个请求,就继续发送下个请求;接收方处理完成后通知发送方,二者步调不一致。

引用知乎大神的话:这两对概念有一定的区别,不能混淆。两对概念的组合,就会产生四个新的概念,同步阻塞、异步阻塞、同步非阻塞、异步非阻塞。

但是,我个人觉得在实际开发中没必须强加区分性的逻辑,因为不管从进程之间还是通信实体之间的角度出发,两对概念之间是有很强的关联的,甚至在简单地场景下,几乎可以互换。

因此本文focus on从一个具体的小场景来分析阻塞/非阻塞、同步/异步的核心思想,此用例灵感来源于实验室师弟。

Linux read()系统调用出发

无论是shell脚本编写者还是C语言开发者,在Linux平台上进行开发/运维工作肯定经常用read()这一方法,read()方法是linux的系统调用,在内核中真正处理它的是sys_read()方法,sys_read()是read系统调用的内核入口(详见博客——https://blog.csdn.net/lwj103862095/article/details/38518069)。

那么用户进程在调用read()时发生了什么?如何区分这一调用是阻塞or非阻塞?又怎么区分用户进程与内核的通信是同步or异步?
上图:
浅谈阻塞/非阻塞、同步/异步——从linux read()系统调用出发_第2张图片

图比较丑,但应该很清晰了。应用进程调用read()方法后,陷入0x80中断,此时如果用户进程挂起等待中断返回,说明这次调用时阻塞的;若马上继续执行后面的代码就是非阻塞的。在linux内核态中,数据需要从网络或者磁盘文件上读出并首先拷贝到内核的虚拟内存空间,然后再从内核的虚存空间拷贝到用户进程的虚存空间。在这一过程中,用户进程可能已经在执行数据处理的代码段,其有两种选择——1)不断轮询内核数据是否已经完成(内核虚存空间->用户进程虚存空间)的拷贝过程;2)直接开始处理数据或执行其它后续逻辑。这里1)就对应进程和内核之间的同步通信,2)则对应异步通信。多说一句,明显同步通信的代价很高,用户进程在轮询(例如while语句)内核,此时系统频繁地在用户态和内核态之间切换,上下文的开销是不容忽视的。当然,linux内核有”IO多路复用“这样的设计来优化这种轮询机制,但优化之后本质上还是同步轮询。

脱离这张图,网络通信中异步通信几乎是绝对主流了,这一方面是因为网络传输的不可靠性,另一方面是用户体验的要求——同步在用户看来就是“卡住了”。所以消息队列及其相关中间件产品(如RabbitMQ, Kafka, RocketMQ, etc.)在web服务器设计中成为了必不可少的部分。

你可能感兴趣的:(编程,开发,常见问题,linux)