5种IO模型

1.概念

IO叫做输入输出,我们可以将IO理解为两步:

  1. 等待IO事件就绪
  2. 数据就绪后进行真正意义上的IO(真正的数据搬迁)
    所以,IO的过程一是等,然后才是输入输出。

进而,我们可以得到评价IO是否高效的标准:

在IO过程中“等”的比重越小的性能越好,越大的性能越低。

IO中有两个重要的函数:read&write
他们也有两个功能:

  1. 获取/写数据

等就是不能读和写,也就是写/读条件不就绪,即发送/接收缓冲区没有被写满,所以读写事件是否就绪通常要与文件描述符相关。

2. 5种IO模型

2.1 A Story

先给大家讲一个钓鱼的故事٩(๑>◡<๑)۶
在这里插入图片描述
风和日丽的一天,张铁锤拿着鱼竿和小板凳,提着桶,来到小河边钓鱼。

他先给鱼竿上放好鱼饵,然后就坐在自己的小板凳上,眼睛一直瞪着鱼竿的尽头。等到鱼上钩后,自己收竿把鱼放在桶里。

不一会,李狗蛋也来了。

他也和王铁锤一样,上好鱼饵之后就等鱼上钩。但他的做法是一边玩手机,一边瞄着鱼竿是否有动静,还不时的和王铁锤搭讪,但王铁锤并不理他,因为他在一动不动的盯着鱼竿,不能分心。

又过了一会,刘翠花也来凑热闹了,

但她放了个大招,直接拿了100多只鱼竿,全固定在岸边。这样几乎不用等,水池边全是她收竿的身影。不一会儿,就钓了好几桶鱼。

今天钓鱼的人真不少,赵二毛也过来了,

但他多拿了一样东西———铃铛,他把铃铛系在鱼竿上,然后坐在小板凳上,拿出了一本《C和指针》看起了书,这样铃铛一响就直接收竿。嗯,文化人就是不一样。

王老五看见这么多人钓鱼,顿时兴致也来了,但人家是老板,还有事要做。

于是吩咐司机让他去钓鱼,钓到了鱼就打电话通知他。只能说,有钱人就是不一样。

好了,我们的故事就讲到这里。

其实,钓鱼的过程和IO非常类似,要先等鱼上钩,然后才真正开始钓鱼。所以,上边的5个人实际上就对应了5种IO模型。

2.2 5种IO模型

用这种讲故事的方式来介绍5种IO模型还是太口语化了,还是让我们严肃一点吧 (。◕ˇ∀ˇ◕)

2.2.1 阻塞IO

对于王铁锤,他一直看着鱼竿,自己等,自己钓,而且等的过程中不做其他的事。这就是阻塞IO:
阻塞式等待IO事件,在等的过程中不能做其他事。
在这里插入图片描述
这个模型也是最容易理解的
程序调用和我们基本的程序编写是一致的:

fd = connect()
write(fd)
read(fd)
close(fd)

程序的read必须在write之后执行,当write阻塞住了,read就不能执行下去

2.2.2 非阻塞IO

对于李狗蛋,他也是自己等,自己钓。但他并不是一直盯着鱼竿,而是时刻检测钓鱼事件是否就绪,没有就做其他的事(玩手机、搭讪)。这就是非阻塞IO:

非阻塞等待,不断检测IO事件是否就绪。没有就绪就可以做其他事。

在这里插入图片描述
从图中可以看出来,这是一个轮询的过程
每次用户询问内核是否有数据报准备好(文件描述符缓冲区是否就绪),当数据报准备好的时候,就进行拷贝数据报的操作。当数据报没有准备好的时候,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一次轮询。

2.2.3 IO复用/多路转接IO

对于刘翠花,她也是自己等,自己钓。但可以同时等待多个鱼上钩事件。这样她钓到鱼的概率就很大,等的时间短,效率明显比其他人高。这就是多路转接IO:

linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数。

在这里插入图片描述
IO复用模型是多了一个select函数,select函数有一个参数是文件描述符集合,意思就是对这些的文件描述符进行循环监听,当某个文件描述符就绪的时候,就对这个文件描述符进行处理。

这种IO模型是属于阻塞的IO。但是由于它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞IO模型高效。

2.2.4 信号驱动IO

对于赵二毛,他将钓鱼事件是否就绪的信息转移到铃铛上,不用自己检测事件就绪,只需要将铃铛作为信号通知方式。这就是信号驱动IO:

linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件。

在这里插入图片描述
信号驱动IO模型是应用进程告诉内核:当你的数据报准备好的时候,给我发送一个信号哈,并且调用我的信号处理函数来获取数据报。这个模型是由信号进行驱动。

2.2.5 异步IO

对于王老五(老板),他并没有经过等的过程,只需要发起事件(让司机钓鱼),然后享受结果。这就是异步IO:

linux中,可以调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。

在这里插入图片描述
异步IO使用的不再是read和write的系统接口了,应用工程序调用aio_XXXX系列的内核接口。

当应用程序调用aio_read的时候,内核一方面去取数据报内容返回,另外一方面将程序控制权还给应用进程,应用进程继续处理其他事务。这样应用进程就是一种非阻塞的状态。

当内核的数据报就绪的时候,是由内核将数据报拷贝到应用进程中,返回给aio_read中定义好的函数处理程序。很少有linux系统支持,windows的IOCP则是此模型

完全异步的I/O复用机制,因为纵观上面其它四种模型,至少都会在由kernel copy data to appliction时阻塞。而该模型是当copy完成后才通知application,可见是纯异步的。好像只有windows的完成端口是这个模型,效率也很出色。

2.3 区别与联系

前4种模型都有等和IO两个阶段,并将数据从内核拷贝到调用者的缓冲区,自己等,自己进行数据搬迁。所以统称为同步IO。 与第5种异步IO相区分。
注:这里同步/异步的概念与进程&线程中的概念不同,不同的背景下应该有不同的理解。

下面是以上五种模型的比较
在这里插入图片描述
可以看出,越往后,阻塞越少,理论上效率也是最优。
阻塞程度:阻塞IO>非阻塞IO>多路复用IO>信号驱动IO>异步IO,效率是由低到高的。

5种模型的比较比较清晰了,剩下的就是把select,epoll,iocp,kqueue按号入座那就OK了。

I/O multiplexing (select and poll and kqueue)
signal driven I/O (SIGIO)
asynchronous I/O (the POSIX aio_functions)

select和iocp分别对应第3种与第5种模型,那么epoll与kqueue呢?其实也于select属于同一种模型,只是更高级一些,可以看作有了第4种模型的某些特性,如callback机制。

那么,

2.3.1 为什么epoll,kqueue比select高级?

因为他们无轮询,因为他们用callback取代了。想想看,当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

2.3.2 windows or unix (IOCP or kqueue/epoll)?

诚然,Windows的IOCP非常出色,目前很少有支持asynchronous I/O的系统,但是由于其系统本身的局限性,大型服务器还是在UNIX下。而且正如上面所述,kqueue/epoll 与 IOCP相比,就是多了一层从内核copy数据到应用层的阻塞,从而不能算作asynchronous I/O类。但是,这层小小的阻塞无足轻重,kqueue与epoll已经做得很优秀了。

3. I/O多路复用的具体的实现

select, poll, epoll 都是I/O多路复用的具体的实现,之所以有这三个鬼存在,其实是他们出现是有先后顺序的。

3.1 select 被实现以后,很快就暴露出了很多问题。

select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
select 只能监视1024个链接,linux 定义在头文件中的,参见FD_SETSIZE。
select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,如果你丧心病狂的竟然关掉这个sock, select的标准行为是不可预测的。

3.2 于是14年以后实现了poll, 修复了select的很多问题

poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。
poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了,所以行走江湖,还是小心为妙。
其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。

但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。

于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll。

3.3 epoll 可以说是I/O 多路复用新的实现,epoll 修复了poll 和select绝大部分问题, 比如

epoll 现在是线程安全的
epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。
可是epoll 有个致命的缺点,只有linux支持。比如BSD上面对应的实现是kqueue。

而ngnix 的设计原则里面, 它会使用目标平台上面最高效的I/O多路复用模型咯,所以才会有这个设置。一般情况下,如果可能的话,尽量都用epoll/kqueue吧。

参考:
https://blog.csdn.net/summy_J/article/details/74474902 (IO概念&5种IO模型介绍)
https://blog.csdn.net/lltaoyy/article/details/54861749 (五种网络io模型)
https://blog.csdn.net/happy_wu/article/details/80052617(IO多路复用—由Redis的IO多路复用)
https://blog.csdn.net/qq_35433716/article/details/85345907 (epoll原理图解)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值