IO模型

作者 Claymore 日期 2017-04-27
web
IO模型

异步IO

何为异步

先谈下什么为异步:

有三个任务正常按时间顺序执行:

多线程去做:

异步去做:

首先,你要知道,一个任务,可能会堵塞在某个地方,比如硬盘读写,数据库操作等等,这样一个程序就必须等待你的操作完成,才能继续下去,这无疑是对计算资源的极大浪费:(下面的图就是同步IO了)

那么如果,你在堵塞的时候,用某种机制,能使你快速脱离堵塞的状态,开启下一个程序,等待你的操作完成,在来继续上一个程序的工作,这个模型就是不堵塞的,这样资源的到了极大的利用,我们的效率自然提升.

异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

在理解并发和并行

系统中有多个任务同时存在可称之为“并发”,系统内有多个任务同时执行可称之为“并行”;

并发是并行的子集。

比如在单核CPU系统上,只可能存在并发而不可能存在并行。上面的异步是就说明了单核的并发。

多核:

并行: 就是等于号

并发: 就是大于号

通俗点说 并发就是不同线程同时干一件事情
并行就是不同线程同时干不同的事情

所以并发会引起资源的竞争。

阻塞和非阻塞

阻塞是:

调用结果返回之前,当前线程会被挂起,只有调用函数得到结果后才返回。

有人会把这个概念和同步联系起来,但是他俩是不同的:

同步是当前线程是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息

非阻塞是:

调用结果返回之前,就是不能立刻得到结果之前,不会阻塞当前线程,而是立刻返回(返回一些状态,告诉你我数据还没准备好)

总结

同步:调用一个函数,没有返回数据前,我死等结果。

异步:调用一个函数,有结果再通知我。

阻塞: 我没有得到数据前,不会返回。

非阻塞: 有没有结果我都立即返回,没有的话,我一会再来。

Linux下的五种IO模型

  1. 阻塞I/O
  2. 非阻塞I/O
  3. I/O复用
  4. 信号驱动I/O
  5. 异步I/O

我们 第一次接触到的网络编程都是从 listen()、send()、recv(接收数据),recvfrom(等待接收数据)等接口开始的。

  • 阻塞I/O

    应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好,一直等待….数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。

  • 非阻塞I/O

    当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。

  • I/O复用

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

  • 信号驱动I/O

    首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

  • 异步IO模型

    当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作

五种io模型的比较:

三种I/O复用的实现方式

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现。

select:

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

​ 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

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

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

poll:

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:

1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。 2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

epoll:

epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

epoll的优点:

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;
​ 即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。