IO的阻塞、非阻塞,同步、异步,BIO,NIO,AIO

参考来自:也谈I/O模型

事先说明:

IO有内存IO、网络IO、磁盘IO三种,通常我们说的是后两者,所以spring webflux适用于有大量网络I/O或者磁盘I/O的情况,例如需要从数据库加载大量数据(数据库可能存在于其他机器上,需要通过网络加载数据),从本地加载音视频文件(磁盘I/O)

阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待。

以文件IO为例,一个IO读过程是文件数据从磁盘→内核缓冲区→用户内存的过程。同步与异步的区别主要在于数据从内核缓冲区→用户内存这个过程需不需要用户进程等待。(网络IO把磁盘换做网卡即可)

IO模型

同步阻塞

去餐馆吃饭,点一个自己最爱吃的盖浇饭,然后在原地等着一直到盖浇饭做好,自己端到餐桌就餐。这就是典型的同步阻塞。当厨师给你做饭的时候,你需要一直在那里等着。

网络编程中,读取客户端的数据需要调用recvfrom。在默认情况下,这个调用会一直阻塞直到数据接收完毕,就是一个同步阻塞的IO方式。这也是最简单的IO模型,在通常fd较少、就绪很快的情况下使用是没有问题的。

同步非阻塞

接着上面的例子,你每次点完饭就在那里等着,突然有一天你发现自己真傻。于是,你点完之后,就回桌子那里坐着,然后估计差不多了,就问老板饭好了没,如果好了就去端,没好的话就等一会再去问,依次循环直到饭做好。这就是同步非阻塞。

这种方式在编程中对socket设置O_NONBLOCK即可。但此方式仅仅针对网络IO有效,对磁盘IO并没有作用。因为本地文件IO就没有被认为是阻塞,我们所说的网络IO的阻塞是因为网路IO有无限阻塞的可能,而本地文件除非是被锁住,否则是不可能无限阻塞的,因此只有锁这种情况下,O_NONBLOCK才会有作用。而且,磁盘IO时要么数据在内核缓冲区中直接可以返回,要么需要调用物理设备去读取,这时候进程的其他工作都需要等待。因此,后续的IO复用和信号驱动IO对文件IO也是没有意义的。

此外,需要说明的一点是nginx和node中对于本地文件的IO是用线程的方式模拟非阻塞的效果的,而对于静态文件的io,使用zero copy(例如sendfile)的效率是非常高的。

IO复用

接着上面的列子,你点一份饭然后循环的去问好没好显然有点得不偿失,还不如就等在那里直到准备好,但是当你点了好几样饭菜的时候,你每次都去问一下所有饭菜的状态(未做好/已做好)肯定比你每次阻塞在那里等着好多了。当然,你问的时候是需要阻塞的,一直到有准备好的饭菜或者你等的不耐烦(超时)。这就引出了IO复用,也叫多路IO就绪通知。这是一种进程预先告知内核的能力,让内核发现进程指定的一个或多个IO条件就绪了,就通知进程。使得一个进程能在一连串的事件上等待。

IO复用的实现方式目前主要有select、poll和epoll。

select和poll的原理基本相同:

注册待侦听的fd(这里的fd创建时最好使用非阻塞)

每次调用都去检查这些fd的状态,当有一个或者多个fd就绪的时候返回

返回结果中包括已就绪和未就绪的fd

相比select,poll解决了单个进程能够打开的文件描述符数量有限制这个问题:select受限于FD_SIZE的限制,如果修改则需要修改这个宏重新编译内核;而poll通过一个pollfd数组向内核传递需要关注的事件,避开了文件描述符数量限制。

此外,select和poll共同具有的一个很大的缺点就是包含大量fd的数组被整体复制于用户态和内核态地址空间之间,开销会随着fd数量增多而线性增大。

select和poll就类似于上面说的就餐方式。但当你每次都去询问时,老板会把所有你点的饭菜都轮询一遍再告诉你情况,当大量饭菜很长时间都不能准备好的情况下是很低效的。于是,老板有些不耐烦了,就让厨师每做好一个菜就通知他。这样每次你再去问的时候,他会直接把已经准备好的菜告诉你,你再去端。这就是事件驱动IO就绪通知的方式-epoll。

epoll的出现,解决了select、poll的缺点:

基于事件驱动的方式,避免了每次都要把所有fd都扫描一遍。

epoll_wait只返回就绪的fd。

epoll使用nmap内存映射技术避免了内存复制的开销。

epoll的fd数量上限是操作系统的最大文件句柄数目,这个数目一般和内存有关,通常远大于1024。

目前,epoll是Linux2.6下最高效的IO复用方式,也是Nginx、Node的IO实现方式。而在freeBSD下,kqueue是另一种类似于epoll的IO复用方式。

此外,对于IO复用还有一个水平触发和边缘触发的概念:

水平触发:当就绪的fd未被用户进程处理后,下一次查询依旧会返回,这是select和poll的触发方式。

边缘触发:无论就绪的fd是否被处理,下一次不再返回。理论上性能更高,但是实现相当复杂,并且任何意外的丢失事件都会造成请求处理错误。epoll默认使用水平触发,通过相应选项可以使用边缘触发。

信号驱动

上文的就餐方式还是需要你每次都去问一下饭菜状况。于是,你再次不耐烦了,就跟老板说,哪个饭菜好了就通知我一声吧。然后就自己坐在桌子那里干自己的事情。更甚者,你可以把手机号留给老板,自己出门,等饭菜好了直接发条短信给你。这就类似信号驱动的IO模型。

流程如下:

开启套接字信号驱动IO功能

系统调用sigaction执行信号处理函数(非阻塞,立刻返回)

数据就绪,生成sigio信号,通过信号回调通知应用来读取数据。

异步非阻塞

之前的就餐方式,到最后总是需要你自己去把饭菜端到餐桌。这下你也不耐烦了,于是就告诉老板,能不能饭好了直接端到你的面前或者送到你的家里(外卖)。这就是异步非阻塞IO了。

对比信号驱动IO,异步IO的主要区别在于:信号驱动由内核告诉我们何时可以开始一个IO操作(数据在内核缓冲区中),而异步IO则由内核通知IO操作何时已经完成(数据已经在用户空间中)。

异步IO又叫做事件驱动IO,在Unix中,POSIX1003.1标准为异步方式访问文件定义了一套库函数,定义了AIO的一系列接口。使用aio_read或者aio_write发起异步IO操作。使用aio_error检查正在运行的IO操作的状态。

网络编程模型

上文讲述了UNIX环境的五种IO模型。基于这五种模型,在Java中,随着NIO和NIO2.0(AIO)的引入,一般具有以下几种网络编程模型:

BIO

NIO

AIO

BIO

BIO是一个典型的网络编程模型,是通常我们实现一个服务端程序的过程,步骤如下:

主线程accept请求阻塞

请求到达,创建新的线程来处理这个套接字,完成对客户端的响应。

主线程继续accept下一个请求

这种模型有一个很大的问题是:当客户端连接增多时,服务端创建的线程也会暴涨,系统性能会急剧下降。因此,在此模型的基础上,类似于 tomcat的bio connector,采用的是线程池来避免对于每一个客户端都创建一个线程。有些地方把这种方式叫做伪异步IO(把请求抛到线程池中异步等待处理)。

NIO

JDK1.4开始引入了NIO类库,这里的NIO指的是Non-blcok IO,主要是使用Selector多路复用器来实现。Selector在Linux等主流操作系统上是通过epoll实现的。

NIO的实现流程,类似于select:

创建ServerSocketChannel监听客户端连接并绑定监听端口,设置为非阻塞模式。

创建Reactor线程,创建多路复用器(Selector)并启动线程。

将ServerSocketChannel注册到Reactor线程的Selector上。监听accept事件。

Selector在线程run方法中无线循环轮询准备就绪的Key。

Selector监听到新的客户端接入,处理新的请求,完成tcp三次握手,建立物理连接。

将新的客户端连接注册到Selector上,监听读操作。读取客户端发送的网络消息。

客户端发送的数据就绪则读取客户端请求,进行处理。

相比BIO,NIO的编程非常复杂。

AIO

JDK1.7引入NIO2.0,提供了异步文件通道和异步套接字通道的实现,是真正的异步非阻塞IO, 对应于Unix中的异步IO。

创建AsynchronousServerSocketChannel,绑定监听端口

调用AsynchronousServerSocketChannel的accpet方法,传入自己实现的CompletionHandler。包括上一步,都是非阻塞的

连接传入,回调CompletionHandler的completed方法,在里面,调用AsynchronousSocketChannel的read方法,传入负责处理数据的CompletionHandler。

数据就绪,触发负责处理数据的CompletionHandler的completed方法。继续做下一步处理即可。

写入操作类似,也需要传入CompletionHandler。

其编程模型相比NIO有了不少的简化。

对比


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容