IO 概念区分
四个相关概念:
这四个概念的含义以及相互之间的区别与联系,并不如很多网络博客所写的那么简单, 通过举一些什么商店购物, 买书买报的例子就能讲清楚。
首先强调一点, 网络上很多博文关于同步/异步, 阻塞非阻塞区别的解释其实都经不起推敲。 例如在 严肃 的这一高赞回答中 , 有如下解释(不准确):
粗一看, 好像同步/ 非同步, 阻塞/非阻塞 是两种维度的概念, 可以分别对待, 但是稍微推敲一下就会发现上述的解释根本难以自圆其说。
所以, 上述的解释是不准确的。 让我们看一下《操作系统概念(第九版)》中有关进程间通信的部分是如何解释的:
翻译一下就是:
进程间的通信是通过 send() 和 receive() 两种基本操作完成的。具体如何实现这两种基础操作,存在着不同的设计。 消息的传递有可能是 阻塞的或 非阻塞的 – 也被称为 同步或 异步的:
上述不同类型的发送方式和不同类型的接收方式,可以自由组合。
---------- 下面对理解同步异步,阻塞非阻塞所需的知识点进行详细叙述---------------------
操作系统为了支持多个应用同时运行,需要保证不同进程之间相对独立(一个进程的崩溃不会影响其他的进程 , 恶意进程不能直接读取和修改其他进程运行时的代码和数据)。 因此操作系统内核需要拥有高于普通进程的权限, 以此来调度和管理用户的应用程序。
于是内存空间被划分为两部分,一部分为内核空间,一部分为用户空间,内核空间存储的代码和数据具有更高级别的权限。内存访问的相关硬件在程序执行期间会进行访问控制( Access Control),使得用户空间的程序不能直接读写内核空间的内存。
上图展示了进程切换中几个最重要的步骤:
几个底层概念的通俗(不严谨)解释:
从上述描述中, 可以看出来, 操作系统在进行进切换时,需要进行一系列的内存读写操作, 这带来了一定的开销:
上图展示了一个进程的不同状态:
我们所说的 “阻塞”是指进程在发起了一个系统调用(System Call) 后, 由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting)状态, 以确保它不会被调度执行, 占用 CPU 资源。
这里再重新审视 阻塞/非阻塞 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 与 异步 I/O 的区别 (非阻塞 IO 在下图中没有绘出).
注意, 上面提到的 非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 都是非阻塞式的行为(non-blocking behavior)。 他们的差异仅仅是返回结果的方式和内容不同。
考虑一个单进程服务器程序, 收到一个 Socket 连接请求后, 读取请求中的文件名,然后读请求的文件名内容,将文件内容返回给客户端。 那么一个请求的处理流程会如下图所示。
在这个过程中, 我们可以看到, CPU 和 硬盘IO 的资源大部分时间都是闲置的。 此时, 我们会希望在等待 I/O 的过程中继续处理新的请求。
方案一: 多进程
方案二:多线程
引申问题: 一个进程中的某一个线程发起了 system call 后, 是否造成整个进程的阻塞? 如果会, 那么多线程方案与单进程方案相比就没有明显的改善。
在这种方案中, 如果 CPU 是多核的, 不同的线程还可以运行在不同的 CPU processor 上。 既实现了IO 并发, 也实现了 CPU 并发。
问题: 内核支持线程可移植性差, 其实现对于不同的操作系统而言有所差别。
从上面的过程可以看出,用户级支持线程(User-Supported Threads)的解决方案基于非阻塞IO系统调用( non-blocking system call) , 且是一种基于操作系统内核事件通知(event-driven)的解决方案, 该方案可以降低系统处理并发请求时的进程切换开销。 基于这个方案, 可以引申到更为宽泛的 event-driven progreamming 话题上。 但是这里就不作赘述了。
2. 非阻塞系统调用(non-blocking I/O system call 与 asynchronous I/O system call) 的存在可以用来实现线程级别的 I/O 并发, 与通过多进程实现的 I/O 并发相比可以减少内存消耗以及进程切换的开销。
作者:萧萧
链接:https://www.zhihu.com/question/19732473/answer/241673170
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处