前言

  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模型。

参考资料:

《Unix网络编程》

《Netty权威指南》李林峰

IO设计模式:Reactor和Proactor对比:http://www.2cto.com/kf/201504/395318.html

Java NIO浅析IO模型:http://www.cnblogs.com/dolphin0520/p/3916526.html

使用异步I/O大大提高应用程序的性能:https://www.ibm.com/developerworks/cn/linux/l-async/

TimeServer源代码:https://git.oschina.net/wangxu/TimeServer

浅析I/O模型及其设计模式的更多相关文章

  1. I/O模型之四:Java 浅析I/O模型(BIO、NIO、AIO、Reactor、Proactor)

    目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...

  2. 浅析css布局模型1

    css是网页的外衣,好不好看全凭css样式,而布局是css中比较重要的部分,下面来分析一下常见的几种布局. 流动模型 流动模型是网页布局的默认模式,也是最常见的布局模式,他有两个特点: 1.块状元素都 ...

  3. Java NIO:浅析I/O模型

    也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗.在进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型.下面本文先从同步和异步的概念 说起, ...

  4. java的nio之:浅析I/O模型

    也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗.在 进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型.下面本文先从同步和异步的概念 说起 ...

  5. Java NIO:浅析I/O模型(转)

    原文链接:http://www.cnblogs.com/dolphin0520/p/3916526.html 以下是本文的目录大纲: 一.什么是同步?什么是异步? 二.什么是阻塞?什么是非阻塞? 三. ...

  6. 浅析I/O模型

    以下是本文的目录大纲: 一.什么是同步?什么是异步? 二.什么是阻塞?什么是非阻塞? 三.什么是阻塞IO?什么是非阻塞IO? 四.什么是同步IO?什么是异步IO? 五.五种IO模型 六.两种高性能IO ...

  7. I/O 模型及其设计模式

    来源:伯乐在线 - 咸菜 链接:http://blog.jobbole.com/104638/ 前言 I/O在软件开发中的重要性无需多言,无论是在操作系统.网络协议.DBMS这种底层支撑软件还是在移动 ...

  8. 浅析 Dapr 里的云计算设计模式

    Dapr 实际上是把分布式系统 与微服务架构实践的挑战以及k8s 这三个主题的全方位的设计组合,特别是Kubernetes设计模式 一书作者Bilgin Ibryam 提出的Multi-Runtime ...

  9. 浅析java内存模型--JMM(Java Memory Model)

    在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...

随机推荐

  1. C# .net中获取台式电脑中串口设备的名称

    来源:http://www.cnblogs.com/hshuzhao/p/4028856.html?utm_source=tuicool&utm_medium=referral .情境: 做项 ...

  2. 如何通过linux ssh远程linux不用输入密码登入

    如何通过一台linux ssh远程其他linux服务器时,不要输入密码,可以自动登入.提高远程效率,不用记忆各台服务器的密码. 工具/原料   ssh,ssh-keygen,scp 方法/步骤   首 ...

  3. JS 获取自定义标签

    <abc-aaa xwe='sdf'>AAAAAAAAAAAAAAAAAAAAAA</abc-aaa> alert($("abc-aaa").attr(&q ...

  4. RSA算法 Android JAVA C#互通

    RSA算法属非对称加密算法,在实际使用中,往往客户端使用公钥进行加密传递敏感数据,服务端server使用私钥进行解密,这样防止中间人从网络获取敏感数据的明文. Android端主要代码如下: pack ...

  5. PropertyPlaceholderConfigurer的用法:

    用法1: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w ...

  6. Linux连续执行多条命令

    引自:这里 每条命令使用";"隔开,则无论前边的命令执行成功与否都会继续执行下一条命令这里,故意将第二条命令中的echo多写了一个o,命令执行出错,但并不影响后续命令的执行可以这么 ...

  7. 【转】 IOS开发xcode报错之has been modified since the precompiled header was built

    本文转载自  IOS开发xcode报错之has been modified since the precompiled header was built 其实我是升级xcode到4.6.3的时候遇到的 ...

  8. English Metric Units and Open XML

    English Metric Units and Open XML 在Open XML里使用了English Metric Units(EMUs)来作为度量单位.比如 public class Ext ...

  9. 数据人员Sql必会列转行

    列转行上一篇博客已经介绍过了. 下面介绍一下行转列的实现 假设我们有一个数据表: CREATE TABLE row_to_line ( ) NOT NULL, -- 学生名称 yingyu integ ...

  10. Jdbc如何从PostgreSql读取海量数据?PostgreSql源代码分析纪录

    前言: 最近做数据同步,需要从PostgreSql获取数据,发现一旦数据比较多,那么读取的速度非常慢,并且内存占用特别多&GC不掉. 代码样例: 为了方便讲解,下面写了事例代码,从b2c_or ...