I/O 模型及其设计模式

来源:伯乐在线 - 咸菜

链接:http://blog.jobbole.com/104638/

前言

I/O在软件开发中的重要性无需多言,无论是在操作系统、网络协议、DBMS这种底层支撑软件还是在移动APP,大型网站服务器等应用软件的开发中都是最核心最重要的部分。特别是现在软件服务使用量和数据量爆炸增长的时代,大数据背景下的高可用分布式系统都离不开高效稳定的I/O。本文就简要分析各类I/O模型的演进、基本原理、应用方法、优缺点及其使用场景。文章最后会简要分析两种常用的I/O设计模式。需要说明的是,对于I/O中的许多问题是没有统一、确切的答案的,因此在分析一些问题的时候会根据自己的理解来说明,很有可能和其他书籍或者文章的观点有出入,至于哪种理解更好欢迎交流。

文章大纲:

1. 阻塞/非阻塞 同步/异步

2. I/O中的阻塞/非阻塞 同步/异步

3. BIO、伪异步I/O、NIO、AIO四种常用I/O模型及其对比

4. Reactor、Proactor两种I/O设计模式及其对比

5. 总结

使用各种I/O模型实现的时间服务器源代码仅供参考:https://git.oschina.net/wangxu/TimeServer

参考《Neety权威指南》

阻塞/非阻塞 & 同步/异步

在介绍I/O模型之前需要先理解几个概念,理解了阻塞/非阻塞 & 同步/异步的联系和区别才能理解I/O模型。关于阻塞/非阻塞 & 同步/异步有很多资料的说法不一致,但是也没必要咬文嚼字,只要能够结合实际的例子来理解每种模型的基本原理就可以了。下面就我自己的理解来说一下这个问题。

阻塞/非阻塞:首先需要知道阻塞/非阻塞是针对某一个事件(线程/进程)来说的。对于阻塞,如果一个事件在触发一个请求后,由于条件不满足,那么这个事件就会停在这个请求上。拿一个线程来说,一个线程在请求了一个系统调用之后,由于当前不满足执行这个请求的条件,那么这个线程就会停在这个请求上,直到请求执行完毕或者出现异常返回,这个线程阻塞的时候操作系统不会分配CPU时间。对于非阻塞,如果一个事件在触发一个请求后,无论当前是否满足执行请求的条件,都会把结果或者异常返回给请求事件,这个事件不会被阻塞。

举个栗子:你想去ATM机取钱,但是前面有人排队,那么这时候你必须要排在后面。在轮到你取钱之前你哪也不能去什么也不能干,只能排队等待。那这就是阻塞式的。那如果你闲ATM人太多,你去了银行大厅到柜台取钱。那么你到了之后,大堂经理会提示你去一张排队号,在你取完排队号之后,你不必非要站在柜台前面等着,你可以坐着玩手机,如果时间够的话你可以上个厕所,吃个饭都是可以的。如果轮到你了,叫号系统就会广播:请xxx号顾客到xxx号窗口办理业务。这时候你听见就可以去取钱了。这就是非阻塞式的。

同步/异步:首先需要强调一点,同步/异步是针对多个事件(线程/进程)来说的。拿单个的事件来谈同步/异步是没有意义的。有点类似于操作系统中进程调度中狭义的同步(和资源互斥相对)。如果事件A需要等待事件B的完成才能完成,这种就可以说是同步的。如果事件A的完成需要事件B的执行结果,但是在B完成之前A不会因为B没有完成而等待,而是继续执行,等待B完成之后自动补全A的任务。类似这种的就是异步的。

举个栗子:还是取钱的例子,如果你像上面说的那两种方法取钱的话,你还是得自己出马,排队/取号,办理取钱业务然后回家,这种都是同步的。但是如果你办了一张银行的VIP金卡,那好了,给银行打个电话说需要多少钱什么时候需要,然后你可以接着干你的事情,就当这件事情不存在一样。银行的业务员会把你需要的钱自动在合适的时间给你送来。那么万一业务员在路上被抢劫了,银行也会给你打电话通知你。像这种就类似异步的操作。

区分阻塞/同步和非阻塞/异步:只要理解了阻塞/非阻塞式针对单一事件,同步/异步是针对多个事件这个核心就能够区分阻塞/同步和非阻塞/异步这两组完全不同的概念。

I/O中的阻塞/非阻塞 & 同步/异步

理解了这几种不同的概念,下面来具体的看一下这几种概念组合起来在I/O中的应用。

同步阻塞I/O:

最常用的一个模型是同步阻塞 I/O 模型。在这个模型中,用户空间的应用程序执行一个系统调用,这会导致应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态,因此从处理的角度来看,这是非常有效的。在调用 read 系统调用时,应用程序会阻塞并对内核进行上下文切换。然后会触发读操作,当响应返回时(从我们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。然后应用程序就会解除阻塞(read 调用返回)。从应用程序的角度来说,read 调用会延续很长时间。实际上,在内核执行读操作和其他工作时,应用程序的确会被阻塞。

阻塞I/O模型

同步非阻塞I/O:

在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read 操作可能会返回一个错误代码,说明read请求不能立即满足。需要应用程序调用许多次来等待操作完成。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,或者试图执行其他工作。这个方法可以引入 I/O 操作的延时,因为数据在内核中变为可用到用户调用read 返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。

非阻塞I/O模型

异步阻塞I/O:

个人觉得谈这种模型的意义不大,以为请求线程已经阻塞,那么异步的作用就不大了。但还有一种理解就是这里的阻塞是通知的阻塞,而不是请求线程的阻塞,也就是一种带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞select 系统调用来确定一个 I/O 描述符何时有操作。使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。

复用I/O模型

异步非阻塞I/O:

异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。在一个进程中为了执行多个 I/O 请求而对计算操作和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。当一个或多个 I/O 请求挂起时,CPU 可以执行其他任务;或者更为常见的是,在发起其他 I/O 的同时对已经完成的 I/O 进行操作。

异步I/O模型

BIO、伪异步I/O、NIO、AIO四种常用I/O模型及其对比

BIO/伪异步IO:BIO是阻塞(block)I/O同时也是同步的,就是说BIO是一种同步阻塞I/O模型,在基于传统的同步阻塞I/O模型的开发中,需要服务端监听端口号,然后客户端通过IP和端口号来和服务端建立TCP连接,以同步阻塞的方法进行数据传输。一般使用BIO的服务端设计中是一客户一线程模型的。

BIO模型

这种模型是有问题的,如果连接客户端较多会大大消耗服务端的资源,如果线程数量超过服务端能承受的最大数量,那么服务器可能会出现很严重的后果。所以出现了伪异步I/O模型,伪异步I/O模型对BIO模型进行了改进,采用了线程池来处理客户连接线程。这样就可以灵活的设置线程池的大小,可以避免服务端资源耗尽的问题。

伪异步I/O模型

但是伪异步I/O模型并没有从根本上解决客户连接线程的阻塞问题,只是对BIO模型进行了简单的优化。面对巨大的连接客户线程还是存在客户线程阻塞时间较长反应较慢的问题。

NIO:NIO是一种非阻塞(non-block)的,同时又是同步的I/O模型。这里只谈一种带有Selector多路复用器的NIO。NIO是使用一个Selector复用器线程来轮询每一个客户端连接,这样就不用阻塞用户线程,同时也不用每个用户线程忙等待。只使用一个线程来轮询I/O事件,这样一来就可以从根本上解决用户线程阻塞的问题。所以NIO模型比较适合高负载、高并发的网络应用。能够充分利用系统资源快速处理请求返回响应消息。NIO适合连接数较多连接时间I/O任务较短的场景,例如即时消息服务器。如果连接数不多而且比较固定,I/O任务较长使用NIO模型就会得不偿失,不仅增加了编程的复杂度而且达不到预期的效果。

AIO:AIO是一种异步(Asyncronous)非阻塞的I/O模型,它需要操作系统内核线程的支持,一个用户线程发起一个系统调用请求后就可以继续执行,内核线程执行完系统调用会根据回调函数来完成处理工作。所以AIO模型应该是一种比较理想的模型,因为操作系统内核线程做了一些工作,所以在编程复杂度上AIO要比NIO要简单一些。AIO比较适合连接数较多其I/O任务比较长的场景。

借助《Netty权威指南》上的一张表对比一下各个I/O模型的特点:

Reactor、Proactor两种I/O设计模式及其对比

Reactor:Reactor模式是基于NIO多路复用I/O模型实现的一种常用的模式。在Reactor模式中每个客户连接会注册自己感兴趣的事件,然后Selector多路复用器会轮询每个就绪事件,每到一个事件执行一个事件。Reactor实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应。Reactor比较适合连接数较多但是任务量较小的场景。Reactor实现相对简单,对于耗时短的处理场景处理高效;操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性;事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁;事务分离,将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来。Reactor同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;但是Reactor不适合执行耗时较长的操作,处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理。

Proactor:Proactor模式是基于AIO实现的一种高效的I/O设计模式。在Proactor中用户连接请求I/O操作,这时操作系统内核就会调用相应的系统调用来完成请求,内核线程在完成用户的I/O请求后把执行结果放在完成事件队列中,Proactor从完成事件队列中取出结果根据相应的回调处理器来完成对操作结果的相应处理。Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)。相对Reactor来说,Proactor性能更高,能够处理耗时长的并发场景;可以异步接收和同时处理多个服务请求的事件驱动程序;但是Proactor依赖操作系统对异步操作的支持,各类操作系统对异步I/O支持的实现细节有差异,没有形成统一的标准。

总结

没有最好的I/O模型,只有最适合的I/O模型。

时间: 2024-12-28 16:16:25

I/O 模型及其设计模式的相关文章

第55课 模型视图设计模式

1. 模型视图模式 (1)模型视图设计模式的核心思想 ①模型(数据)与视图(显示)相分离 ②模型对外提供标准接口存取数据(不关心数据如何显示) ③视图自定义数据的显示方式(不关心数据如何组织存储) (2)模型视图模式的直观理解 (3)模型视图模式的工作机制 ①当数据发生改变时,模型发出信号通知视图 ②当用户与视图进行交互时,视图发出信号提供交互信息 2. Qt中的模型-视图类层次结构 (1)Qt中的模型类的层次结构 (2)Qt中的视图类的层次结构 3. 关键技术问题 (1)模型如何为数据提供统一

第57课 模型视图设计模式(下)

1. Qt中标准模型定义 (1)预期的模型逻辑结构 (2)代码实现 //main.cpp #include "Widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); } //Widget.h #ifndef WIDGET_H #define WIDGET_H #in

字典转模型KVC和runtime二者实现与区别

我们知道在开发中,字典转模型是一种很常用的设计模式,当字典中元素的个数比较少的时候,我们可以直接用dic[key]去给模型对象的属性赋值,但是当字典中元素的个数比较多的时候,再用前面的解决方法就不行了,所以就有了KVC(key value coding:键值编码)字典转模型的设计模式. KVC的设计原理: [item setValue:@"value" forKey:@"property"]: 1.首先去模型中查找有没有setProperty,找到,直接调用赋值 [

第62课 模型视图中的委托(下)

1. 委托的本质 (1)为视图提供数据编辑的上下文环境 (2)产生界面元素的工厂类 (3)能够使用和设置模型中的数据 2. 自定义委托类 (1)自定义委托类的继承关系 (2)自定义委托时需要重写的函数 ①createEditor ②updateEditorGeometry ③setEditorData ④setModelData ⑤paint(可选) (3)自定义委托时重写的函数由谁调用? 由于模型视图设计模式,视图中组合了委托对象,既然委托存在于视图内部,就应该由视图来调用(可从上图的函数调用

第61课 模型视图中的委托(上)

1. Qt模型视图对用户输入的处理 (1)传统的MVC设计模式 (2)Qt中的模型视图设计模式如何处理用户输入? ①视图中集成了处理用户输入的功能(即委托) ②视图将用户输入作为内部独立的子功能来实现 ③模型负责组织数据,视图负责显示数据,委托则用于编辑修改数据. 2. 模型视图中的委托 (1)委托(Delegate)是视图中处理用户输入的部件(如编辑框.单选按钮等) (2)视图可以设置委托对象用于处理用户输入 (3)委托对象负责创建和显示用户输入的上下文(内容),如编辑框的创建和显示. (4)

[转] 如何应用设计模式设计你的足球引擎(一和二)----Design Football Game(Part I and II)

原文地址: http://www.codeproject.com/KB/architecture/applyingpatterns.aspx 作者:An 'OOP' Madhusudanan 译者:赖勇浩(http://blog.csdn.net/lanphaday ) 译者说:这是一篇非常好的文章,有非常棒的例子,非常棒的文笔,非常棒的代码(VB.net编写的,但你肯定读得懂),如果你还不懂设计模式,那它肯定是最适合你的 DPs 文章之一. 第一部分 解决方案架构师:你可以尝试使用模式 愚蠢的

java常考面试题

p { margin-bottom: 0.1in; direction: ltr; line-height: 120%; text-align: justify; orphans: 0; widows: 0 } 什么是Java虚拟机?为什么Java被称作是"平台无关的编程语言"? 参考答案 Java虚拟机是一个可以执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件. Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重

115个Java面试题和答案

面向对象编程(OOP) Java是一个支持并发.基于类和面向对象的计算机编程语言.下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改. 代码复用. 增强代码的可靠性和灵活性. 增加代码的可理解性. 面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象.下面的章节我们会逐个分析这些特性. 封装 封装给对象提供了隐藏内部特性和行为的能力.对象提供一些能被其他对象访问的方法来改变它内部的数据.在Java当中,有3种修饰符:public,private和protected.每一种修

前端MVC学习总结——AngularJS验证、过滤器

前端MVC学习总结--AngularJS验证.过滤器 目录 一.验证 二.过滤器 2.1.内置过滤器 2.1.1.在模板中使用过滤器 2.1.2.在脚本中调用过滤函数 2.2.自定义过滤器 三.指令(directive) 3.1.支持AngularJS功能的指令 3.1.1.应用与模块(ng-app) 3.1.2.控制器(ng-Controller) 3.1.3.包含(ng-Include) 3.1.4.不绑定(ngNonBindable) 3.2.扩展表单元素的指令 3.2.1.ng-opti