计算机原理—同步/异步,阻塞/非阻塞深度解析
cac55 2024-10-11 11:02 25 浏览 0 评论
网络IO作为网络编程核心基础知识,是所有程序员都需要理解掌握的,最近小伙伴在星球群里对这些基本概念理解比较混乱,大多数人可能都没有认真深入去理解过这些基础概念,之前我直播时候说过:内功深厚其实就是对这些基础概念的理解深度,我们学习和工作都是在不断加深对这些基础知识的理解。
四个相关概念:
- 同步(Synchronous)
- 异步( Asynchronous)
- 阻塞( Blocking )
- 非阻塞( Nonblocking)
这四个概念的含义以及相互之间的区别与联系,并不如很多网络博客所写的那么简单, 通过举一些什么商店购物, 买书买报的例子就能讲清楚。
首先强调一点, 网络上的很多博文关于同步/异步, 阻塞非阻塞区别的解释其实都很经不起推敲。例如怎样理解阻塞非阻塞与同步异步的区别这一高赞回答中 , 有如下解释(不准确):
同步/异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 。
所谓同步,就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。
异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果
阻塞/非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
粗一看, 好像同步/ 非同步, 阻塞/非阻塞 是两种维度的概念, 可以分别对待, 但是稍微推敲一下就会发现上述的解释根本难以自圆其说。
- 如果同步是发起了一个调用后, 没有得到结果之前不返回, 那它毫无疑问就是被"阻塞"了(即调用进程处于 “waiting” 状态)。
- 如果调用发出了以后就直接返回了, 毫无疑问, 这个进程没有被“阻塞”。
所以, 上述的解释是不准确的。让我们看一下《操作系统概念(第九版)》中有关进程间通信的部分是如何解释的:
翻译一下就是:
进程间的通信时通过 send() 和 receive() 两种基本操作完成的。具体如何实现这两种基础操作,存在着不同的设计。
消息的传递有可能是阻塞的或非阻塞的 -- 也被称为同步或异步的:阻塞式发送(blocking send). 发送方进程会被一直阻塞, 直到消息被接受方进程收到。
非阻塞式发送(nonblocking send)。发送方进程调用 send() 后, 立即就可以其他操作。
阻塞式接收(blocking receive) 接收方调用 receive() 后一直阻塞, 直到消息到达可用。
非阻塞式接受(nonblocking receive) 接收方调用 receive() 函数后, 要么得到一个有效的结果, 要么得到一个空值, 即不会被阻塞。
上述不同类型的发送方式和不同类型的接收方式,可以自由组合。
- 也就是说, 从进程级通信的维度讨论时, 阻塞和同步(非阻塞和异步)就是一对同义词, 且需要针对发送方和接收方作区分对待。
相关视频推荐
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
先修知识
用户空间和内核空间
进程切换
- 系统调用(system call)
- 中断(interrupt)
进程的阻塞
用户空间和内核空间
操作系统为了支持多个应用同时运行,需要保证不同进程之间相对独立(一个进程的崩溃不会影响其他的进程 , 恶意进程不能直接读取和修改其他进程运行时的代码和数据)。因此操作系统内核需要拥有高于普通进程的权限, 以此来调度和管理用户的应用程序。
于是内存空间被划分为两部分,一部分为内核空间,一部分为用户空间,内核空间存储的代码和数据具有更高级别的权限。内存访问的相关硬件在程序执行期间会进行访问控制( Access Control),使得用户空间的程序不能直接读写内核空间的内存。
- 有《微机原理》 课程基础同学可以 Google 搜索 DPL, CPL 这两个关键字了解硬件层面的内存访问权限控制细节
进程切换
上图展示了进程切换中几个最重要的步骤:
- 当一个程序正在执行的过程中, 中断(interrupt) 或 系统调用(system call) 发生可以使得 CPU 的控制权会从当前进程转移到操作系统内核。
- 操作系统内核负责保存进程 i 在 CPU 中的上下文(程序计数器, 寄存器)到 PCBi (操作系统分配给进程的一个内存块)中。
- 从PCBj取出进程 j 的CPU上下文, 将CPU控制权转移给进程 j , 开始执行进程 j 的指令。
几个底层概念的通俗解释:
- 中断(interrupt)
- CPU 微处理器有一个中断信号位, 在每个CPU时钟周期的末尾, CPU会去检测那个中断信号位是否有中断信号到达, 如果有, 则会根据中断优先级决定是否要暂停当前执行的指令, 转而去执行处理中断的指令。(其实就是 CPU 层级的 while 轮询)
- 时钟中断( Clock Interrupt )
- 一个硬件时钟会每隔一段(很短)的时间就产生一个中断信号发送给 CPU,CPU 在响应这个中断时, 就会去执行操作系统内核的指令, 继而将 CPU 的控制权转移给了操作系统内核, 可以由操作系统内核决定下一个要被执行的指令。
- 系统调用(system call)
- system call 是操作系统提供给应用程序的接口。用户通过调用 systemcall 来完成那些需要操作系统内核进行的操作, 例如硬盘, 网络接口设备的读写等。
从上述描述中, 可以看出来, 操作系统在进行进切换时,需要进行一系列的内存读写操作, 这带来了一定的开销:
- 对于一个运行着 UNIX 系统的现代 PC 来说, 进程切换至少需要花费 300 us 的时间。
进程阻塞
上图展示了一个进程的不同状态:
- New:进程正在被创建.
- Running: 进程的指令正在被执行
- Waiting.:进程正在等待一些事件的发生(例如 I/O 的完成或者收到某个信号)。
- Ready.:进程在等待被操作系统调度。
- Terminated: 进程执行完毕(可能是被强行终止的)。
我们所说的 “阻塞”是指进程在发起了一个系统调用(System Call) 后, 由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting)状态, 以确保它不会被调度执行, 占用 CPU 资源。
友情提示:在任意时刻, 一个 CPU 核心上(processor)只可能运行一个进程 。
这里再重新审视 阻塞/非阻塞 IO 这个概念, 其实阻塞和非阻塞描述的是进程的一个操作是否会使得进程转变为“等待”的状态, 但是为什么我们总是把它和 IO 连在一起讨论呢?
原因是, 阻塞这个词是与系统调用 System Call 紧紧联系在一起的, 因为要让一个进程进入 等待(waiting) 的状态, 要么是它主动调用 wait() 或 sleep() 等挂起自己的操作, 另一种就是它调用 System Call, 而 System Call 因为涉及到了 I/O 操作, 不能立即完成, 于是内核就会先将该进程置为等待状态, 调度其他进程运行, 等到它所请求的 I/O 操作完成了以后, 再将其状态更改回 ready 。
操作系统内核在执行 System Call 时, CPU 需要与 IO 设备完成一系列物理通信上的交互, 其实再一次会涉及到阻塞和非阻塞的问题, 例如, 操作系统发起了一个读硬盘的请求后, 其实是向硬盘设备通过总线发出了一个请求,它即可以阻塞式地等待IO 设备的返回结果,也可以非阻塞式的继续其他的操作。在现代计算机中,这些物理通信操作基本都是异步完成的, 即发出请求后, 等待 I/O 设备的中断信号后, 再来读取相应的设备缓冲区。但是,大部分操作系统默认为用户级应用程序提供的都是阻塞式的系统调用 (blocking systemcall)接口, 因为阻塞式的调用,使得应用级代码的编写更容易(代码的执!行顺序和编写顺序是一的)。
但同样, 现在的大部分操作系统也会提供非阻塞I/O 系统调用接口(Nonblocking I/O system call)。一个非阻塞调用不会挂起调用程序, 而是会立即返回一个值, 表示有多少bytes 的数据被成功读取(或写入)。
非阻塞I/O 系统调用( nonblocking system call )的另一个替代品是异步I/O系统调用 (asychronous system call)。与非阻塞 I/O 系统调用类似,asychronous system call也是会立即返回, 不会等待 I/O 操作的完成, 应用程序可以继续执行其他的操作, 等到 I/O 操作完成了以后,操作系统会通知调用进程(设置一个用户空间特殊的变量值或者触发一个 signal 或者 产生一个软中断 或者 调用应用程序的回调函数)。
此处, 非阻塞I/O 系统调用( nonblocking system call ) 和异步I/O系统调用 (asychronous system call)的区别是:
- 一个非阻塞I/O 系统调用 read() 操作立即返回的是任何可以立即拿到的数据, 可以是完整的结果, 也可以是不完整的结果, 还可以是一个空值。
- 而异步I/O系统调用 read()结果必须是完整的, 但是这个操作完成的通知可以延迟到将来的一个时间点。
下图展示了同步I/O 与 异步 I/O 的区别 (非阻塞 IO 在下图中没有绘出).
注意, 上面提到的 非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 都是非阻塞式的行为(non-blocking behavior)。他们的差异仅仅是返回结果的方式和内容不同。
当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。
非阻塞 I/O 如何帮助服务器提高吞吐量
考虑一个单进程服务器程序, 收到一个 Socket 连接请求后, 读取请求中的文件名,然后读请求的文件名内容,将文件内容返回给客户端。那么一个请求的处理流程会如下图所示。
- R 表示读操作
- W 表示写操作
- C 表示关闭操作
在这个过程中, 我们可以看到, CPU 和 硬盘IO 的资源大部分时间都是闲置的。此时, 我们会希望在等待 I/O 的过程中继续处理新的请求。
方案一:多进程
- 每到达一个请求, 我们为这个请求新创建一个进程来处理。这样, 一个进程在等待 IO 时, 其他的进程可以被调度执行, 更加充分地利用 CPU 等资源。
- 问题:每新创建一个进程都会消耗一定的内存空间, 且进程切换也会有时间消耗, 高并发时, 大量进程来回切换的时间开销会变得明显起来。
方案二:多线程
- 和多进程方案类似,为每一个请求新建一个线程进行处理,这样做的重要区别是, 所有的线程都共享同一个进程空间
- 问题:需要考虑是否需要为特定的逻辑使用锁。
引申问题:一个进程中的某一个线程发起了 system call 后, 是否造成整个进程的阻塞?如果会, 那么多线程方案与单进程方案相比就没有明显的改善。
- 解决办法1:内核支持的线程(kenerl supported threads)
- 操作系统内核能够感知到线程, 每一个线程都会有一个内核调用栈(kenerl stack) 和 保存CPU 寄存器下文的 table 。
在这种方案中, 如果 CPU 是多核的, 不同的线程还可以运行在不同的 CPU processor 上。既实现了IO 并发, 也实现了 CPU 并发。
问题:基于内核线程编写的应用会难以移植
- 不同的操作系统对于内核线程的支持方式统而言有所差别,甚至部分操作系统甚至不支持内核级别线程, 当应用代码基于内核线程进行开发后, 就使得应用层代码与特定的操作系统产生了耦合关系, 不能随意部署
- 解决办法2:用户支持的线程(user supported threads)
- 应用程序基于线程库 thread libray 编写
- 线程库中包含 “虚假的” read(), write(), accept()等系统调用。
- 线程库中的 read(), write(), accept() 的底层实现为非阻塞系统调用(Non-blocking system call), 调用后,由于可以立即返回, 则将特定的线程状态标记为 waiting, 调度其他的可执行线程。内核完成了 IO 操作后, 调用线程库的回调函数, 将原来处于 waiting 状态的线程标记为 runnable.
- 内核感知不到用户线程, 每一个用户的进程拥有一个调度器, 该调度器可以感知到线程发起的系统调用, 当一个线程产生系统调用时, 不阻塞整个进程, 切换到其他线程继续运行。当 I/O 调用完成以后, 能够重新唤醒被阻塞的线程。
- 实现细节:
从上面的过程可以看出,用户支持线程的解决方案基于非阻塞IO系统调用( non-blocking system call) , 且是一种基于操作系统内核事件通知(event-driven)的解决方案, 基于这个流程, 可以引申到更为宽泛的 event-driven progreamming 话题上。但是这里就不作赘述了。
为什么容易混淆
因为这个问题是一个多层次的问题.
举个例子, 一个网络包从APP-A发到APP-B需要经历:
- 从A的业务代码到A的软件框架;
- 从A的软件框架到计算机的操作系统内核;
- 从A所在计算机的内核到网卡;
- 从网卡经过网线发到交换机等设备,层层转发,到达B所在计算机的网卡;
- 从B所在计算机的网卡到B所在计算机的内核;
- 从B所在计算机的内核到B的程序的用户空间;
- 从B的软件框架到B的业务代码;
这里有一个层级关系, 只有在单独一级的调用上, 可以说同步还是异步的问题. 1和7是取决于框架的设计. 2和6见上面分析. 3和5一般是异步的. 4, 以太网是个同步时序逻辑,随信号传输时钟,必须两边设备同时就绪了才能开始传输数据,这又是同步的。
简单地说分三个层次
操作系统层次
操作系统进行IO和任务调用的层次, 现代操作系统通常使用异步非阻塞方式进行IO.
线程层次
操作系统为了减轻程序员的思考负担,将底层的异步非阻塞的IO方式进行封装,把相关系统调用(如read,write等)以同步的方式展现出来。然而,同步阻塞的IO会使线程挂起,同步非阻塞的IO会消耗CPU资源在轮询上。为了解决这一问题,就有3种思路:
- 多线程(同步阻塞);
- IO多路复用(select,poll,epoll)(同步非阻塞,严格地来讲,是把阻塞点改变了位置);
- 直接暴露出异步的IO接口,如Linux kernel-aio(io_uring)和windows IOCP(异步非阻塞)。
业务代码感知层次
这就要看具体的语言, 软件框架了. 如Node.js就通过回调函数, 提供了异步非阻塞的API. 因此,我们在讨论同步、异步、阻塞、非阻塞时,必须先明确是在哪个层次进行讨论。比如node.js,我们可以说她在业务代码感知层次提供了异步非阻塞的API,也可以说在Linux下,她在线程层次以同步非阻塞的epoll来实现。
总结
阻塞/非阻塞, 同步/异步的概念要注意讨论的上下文:
- 阻塞/非阻塞核心区别就是看当前任务有没有被挂起。
- 在进程通信层面, 阻塞/非阻塞, 同步/异步基本是同义词, 但是需要注意区分讨论的对象是发送方还是接收方。发送方阻塞/非阻塞(同步/异步)和接收方的阻塞/非阻塞(同步/异步) 是互不影响的。
- 在 IO 系统调用层面( IO system call )层面, 非阻塞IO 系统调用 和 异步IO 系统调用存在着一定的差别, 它们都不会阻塞进程, 但是返回结果的方式和内容有所差别, 但是都属于非阻塞系统调用( non-blocing system call )。
- 阻塞和非阻塞是等待I/O的期间能不能做其他事情, 自己会不会被挂起, 是关注自己的状态,同步异步是是否需要主动询问, 描述的是行为方式(通信机制)。
- 非阻塞系统调用(non-blocking I/O system call 与 asynchronous I/O system call) 的存在可以用来实现线程级别的 I/O 并发, 与通过多进程实现的 I/O 并发相比可以减少内存消耗以及进程切换的开销。
相关推荐
- 三星电视通过VGA端口接连笔记本教程
-
科技的发达不仅仅表现于智能电视的功能有多全面,手机、笔记本、电视盒子有多智能,而是这些电子产品是否能够相互连接、多屏互动进而进行多媒体播放与数据传输等。目前,三星电视早已可以实现多项设备间的连接与使用...
- KVM210UA 2口USB电脑切换器VGA KVM切换器 键鼠控制2台电脑 免电源
-
登昌恒KVM210UA2口USB电脑切换器VGAKVM切换器键鼠控制2台电脑免外接电源即时切换显示画面不延迟热插拔免驱动适合MAC苹果电脑笔记本和台式电脑...
- 大神自己将老旧笔记本改装成多方面强悍的“超极本”
-
文/自己建房子亲戚有个03年的清华紫光笔记本,配置是奔腾1.8G,XP勉强运行,亲戚今年帮了非常大的忙,无以回报,把他这个垃圾本改一下略为回报新配置:CPU是i53470t的,内存是4G两条组成双...
- 正点原子开拓者FPGA开发板资料连载第二十二章RGBTFT-LCD彩条显示
-
1)实验平台:正点原子开拓者FPGA开发板2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子3)全套实验源码+手册+视频下载地址:http://www.openedv.c...
- 显卡引出故障现象及排除(造成显卡故障的原因有哪些)
-
故障现象一:显示器黑屏故障排除:1、先确定是否是显卡有问题。判断的方法是听PC喇叭的叫声,一长两短声肯定是显卡发生了致命错误,只能更换。2、如果无法断定显卡的好坏,可以换一块试试,还不行的话则有几种情...
- 用FPGA/树莓派RP2040驱动HDMI的资源
-
如果能够用FPGA能够驱动显示器,就可以做不少与视频相关的实验,很多FPGA开发板使用了比较简单的VGA接口,连接非常简单,如下图:然后我们就可以用FPGA的逻辑来做各种游戏了:然而,现在的显示器基本...
- STM32+A3P125 图形控制器方案,多参数监护仪数据采集模块深度解析
-
一、生理信号采集与转换架构多参数监护仪数据采集模块是将人体生理信号转化为数字信号的核心单元,其架构遵循“传感器感知→信号调理→模数转换→数字处理”的标准化流程:1.传感器类型与生理信号转换传感器...
- 电脑重启都提示AMD显卡错误“No AMD graphics driver is..”解决方法
-
电脑重装完系统后,每次重启都提示AMD显卡错误“NoAMDgraphicsdriveris......”怎么办?网上相关问题解决很多人方法是打驱动,可是有些是驱动打好了还是反复出现NoAM...
- ipad1改造显示器之拆解篇(ipadair改显示器)
-
闲来无事,翻出来家里12年前的初代ipad,除了能正常充电开机盖泡面之外,好像啥也干不了……上网一搜有许多改成显示器的案例,按耐不住的激动的小手也想跃跃欲试一番,说干就干,先从拆解开始。注意,以hom...
- 老电脑显卡驱动装不上?降级旧版驱动一招解决!
-
老电脑显卡驱动装不上?降级旧版驱动一招解决!今天给家里的老台式机重装系统遇到了麻烦:i34130处理器配上HD7770显卡,装完Win7后,显卡驱动死活装不对!问题表现:最初状态:设备管理器“显...
- 想淘汰VGA 先得知道这些门道(vga是不是淘汰了)
-
不要说新显卡,就是在主板上,VGA接口也越来越少,眼看就被彻底淘汰了。当然小伙伴们的显示器应该早就不是只有VGA接口的旧型号了,彻底淘汰VGA就换个接口或者弄个转接呗,可是……真的这么简单吗?小编今天...
- IMX258摄像头驱动(摄像头最新驱动)
-
IMX25813MPOISUSBCamera(A)说明IMX25813MPOISUSBCamera(A)功能简介特性USB通用摄像头1300万像素接口USB...
- 朗强科技:VGA视频信号分配原理分析
-
VGA分配器的主要功能是把一个视频信号重新分配成多个信号。高分辨率的视频分配放大器的作用是在接收一个来自计算机视频端口的信号后将其进行放大处理,并且在保持原有信号质量的情况下,将其信号分配到多个高分辨...
- 视频矩阵的应用及分类(视频矩阵的应用及分类)
-
视频矩阵-基本功能和要求视频矩阵-基本功能和要求 一个矩阵系统通常还应该包括以下基本功能:字符信号叠加;解码器接口以控制云台和摄像机;报警器接口;控制主机,以及音频控制箱、报警接口箱、控制键盘等附件...
- 告别蓝屏烦恼,Windows系统崩溃急救指南!
-
Windows系统蓝屏(BlueScreenofDeath,简称BSoD)是Windows操作系统在无法从系统错误中恢复时显示的屏幕图像,通常会伴随一个或多个错误代码,这些代码是诊断问题的关键线...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 三星电视通过VGA端口接连笔记本教程
- KVM210UA 2口USB电脑切换器VGA KVM切换器 键鼠控制2台电脑 免电源
- 大神自己将老旧笔记本改装成多方面强悍的“超极本”
- 正点原子开拓者FPGA开发板资料连载第二十二章RGBTFT-LCD彩条显示
- 显卡引出故障现象及排除(造成显卡故障的原因有哪些)
- 用FPGA/树莓派RP2040驱动HDMI的资源
- STM32+A3P125 图形控制器方案,多参数监护仪数据采集模块深度解析
- 电脑重启都提示AMD显卡错误“No AMD graphics driver is..”解决方法
- ipad1改造显示器之拆解篇(ipadair改显示器)
- 老电脑显卡驱动装不上?降级旧版驱动一招解决!
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)