阻塞非阻塞与同步异步的理解

网上一大堆关于这四个概念的解释,有很多都不够深入。它们并不是很多网络博客所写的那么简单。以下内容为网上整理并加上本人理解。

1 了解一下前置知识

1.1 相关概念:

  • 同步(Synchronous)
  • 异步( Asynchronous)
  • 阻塞( Blocking )
  • 非阻塞( Nonblocking)

1.2 用户空间和内核空间

操作系统为了支持多个应用同时运行,需要保证不同进程之间相对独立。 因此操作系统内核需要拥有高于普通进程的权限,以此来调度和管理用户的应用程序于是内存空间被划分为两部分,一部分为内核空间,一部分为用户空间,内核空间存储的代码和数据具有更高级别的权限。内存访问的相关硬件在程序执行期间会进行访问控制( Access Control),使得用户空间的程序不能直接读写内核空间的内存。

1.3 进程调度

详细可参考进程调度文章。

进程切换中几个最重要的步骤:当一个程序正在执行的过程中,中断系统调用发生可以使得 CPU 的控制权会从当前进程转移到操作系统内核。操作系统内核负责保存进程 i 在 CPU 中的上下文(程序计数器, 寄存器)到 PCBi (操作系统分配给进程的一个内存块)中。从 PCBj 取出进程 j 的CPU 上下文, 将 CPU 控制权转移给进程 j ,开始执行进程 j 的指令。

几个底层概念的通俗解释:

  • 中断: CPU 微处理器有一个中断信号位, 在每个CPU时钟周期的末尾, CPU会去检测那个中断信号位是否有中断信号到达, 如果有, 则会根据中断优先级决定是否要暂停当前执行的指令, 转而去执行处理中断的指令。 (其实就是 CPU 层级的 while 轮询)
  • 时钟中断: 一个硬件时钟会每隔一段(很短)的时间就产生一个中断信号发送给 CPU,CPU 在响应这个中断时, 就会去执行操作系统内核的指令, 继而将 CPU 的控制权转移给了操作系统内核, 可以由操作系统内核决定下一个要被执行的指令。
  • 系统调用: 是操作系统提供给应用程序的接口。用户通过系统调用来完成那些需要操作系统内核进行的操作,例如硬盘,网络接口设备的读写等。

操作系统在进行进切换时,需要进行一系列的内存读写操作, 这带来了一定的开销:对于一个运行着 UNIX 系统的现代 PC 来说, 进程切换通常至少需要花费 300 us 的时间

1.4 进程阻塞

在这里插入图片描述
上图展示了一个进程的不同状态:

  • New. 进程正在被创建.
  • Running. 进程的指令正在被执行
  • Waiting. 进程正在等待一些事件的发生(例如 I/O 的完成或者收到某个信号)
  • Ready. 进程在等待被操作系统调度
  • Terminated. 进程执行完毕(可能是被强行终止的)

我们所说的 “阻塞”是指进程在发起了一个系统调用(System Call) 后, 由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting)状态, 以确保它不会被调度执行, 占用 CPU 资源。

友情提示: 在任意时刻,一个CPU 核心上(processor)只可能运行一个进程 。

2 I/O System Call 的阻塞/非阻塞,同步/异步

阻塞和非阻塞描述的是进程的一个操作是否会使得进程转变为“等待”的状态, 但是为什么我们总是把它和 IO 连在一起讨论呢?原因是, 阻塞这个词是与系统调用 System Call 紧紧联系在一起的, 因为要让一个进程进入等待waiting的状态, 要么是它主动调用 wait() 或 sleep() 等挂起自己的操作, 另一种就是它调用 System Call, 而 System Call 因为涉及到了 I/O 操作, 不能立即完成, 于是内核就会先将该进程置为waiting状态, 调度其他进程的运行, 等到它所请求的 I/O 操作完成了以后,再将其状态更改回ready

操作系统内核在执行 System Call 时, CPU需要与IO设备完成一系列物理通信上的交互。 例如,操作系统发起了一个读硬盘的请求后,其实是向硬盘设备通过总线发出了一个请求,它即可以阻塞式地等待IO 设备的返回结果,也可以非阻塞式的继续其他的操作。在现代计算机中,这些物理通信操作基本都是异步完成的,即发出请求后,等待 I/O 设备的中断信号后, 再来读取相应的设备缓冲区。 但是,大部分操作系统默认为用户级应用程序提供的都是阻塞式的系统调用接口, 因为阻塞式的调用,使得应用级代码的编写更容易(代码的执行顺序和编写顺序是一致的)。

但同样, 现在的大部分操作系统也会提供非阻塞I/O系统调用接口。一个非阻塞调用不会挂起调用程序, 而是会立即返回一个值, 表示有多少bytes 的数据被成功读取(或写入)。非阻塞I/O 系统调用的另一个替代品是异步I/O系统调用。 与非阻塞 I/O 系统调用类似,异步I/O系统调用也是会立即返回, 不会等待 I/O 操作的完成, 应用程序可以继续执行其他的操作, 等到 I/O 操作完成了以后,操作系统会通知调用进程(设置一个用户空间特殊的变量值 或者 触发一个 signal 或者 产生一个软中断 或者 调用应用程序的回调函数)。

此处,非阻塞I/O系统调用和异步I/O系统调用的区别是:一个非阻塞I/O 系统调用 read() 操作立即返回的是任何可以立即拿到的数据, 可以是完整的结果,也可以是不完整的结果,还可以是一个空值。而异步I/O系统调用 read()结果必须是完整的,但是这个操作完成的通知可以延迟到将来的一个时间点。

3 如何帮助服务器提高吞吐量

3.1 多进程

3.1.1 说明:

每到达一个请求,我们为这个请求新创建一个进程来处理。这样,一个进程在等待 IO 时,其他的进程可以被调度执行,更加充分地利用 CPU 等资源。

3.1.2 问题:

每新创建一个进程都会消耗一定的内存空间,且进程切换也会有时间消耗,高并发时,大量进程来回切换的时间开销会变得明显起来。

3.2 多线程

3.2.1 说明:

和多进程方案类似,为每一个请求新建一个线程进行处理,这样做的重要区别是, 所有的线程都共享同一个进程空间。

3.2.2 问题:

共享变量之后必然要考虑为特定的逻辑使用锁。

3.2.3 引申问题:

一个进程中的某一个线程发起了 system call 后,是否造成整个进程的阻塞? 用户级线程会导致整个进程阻塞! 这样多线程方案与单进程方案相比就没有明显的改善。而内核级线程不会

3.3 解决办法:

3.3.1 内核支持的线程(kenerl supported threads)
  • 作系统内核能够感知到线程,每一个线程都会有一个内核调用栈(kenerl stack)和保存CPU 寄存器下文的table 。
  • 在这种方案中,如果 CPU 是多核的,不同的线程还可以运行在不同的 CPU processor 上。既实现了IO 并发,也实现了 CPU 并发。
  • 问题:内核支持线程可移植性差,其实现对于不同的操作系统而言有所差别。
3.3.2 用户支持的线程(user supported threads)
  • 内核感知不到用户线程,每一个用户的进程拥有一个调度器,该调度器可以感知到线程发起的系统调用,当一个线程产生系统调用时,不阻塞整个进程,切换到其他线程继续运行。当 I/O 调用完成以后,能够重新唤醒被阻塞的线程。
  • 实现细节: 应用程序基于线程库 thread libray 编写线程库中包含 “虚假的” read(), write(), accept()等系统调用。线程库中的 read(), write(), accept() 的底层实现为非阻塞系统调用,调用后,由于可以立即返回,则将特定的线程状态标记为 waiting, 调度其他的可执行线程。内核完成了 IO 操作后,调用线程库的回调函数,将原来处于 waiting 状态的线程标记为 runnable。

参考 :https://www.zhihu.com/question/19732473 萧萧的回答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值