文章目录
- IO模型
- 阻塞IO
- 非阻塞IO
- 信号驱动IO
- 多路复用IO
- 异步IO
IO模型
根据各自的特性不同,IO模型被分为阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO五类。
最主要的两个区别就是阻塞与非阻塞,同步与异步。
阻塞与非阻塞
阻塞与非阻塞最主要的区别就是程序在等待调用结果时的状态。
- 阻塞: 为了完成一个功能发起调用,如果不具备完成功能的条件,则调用会一直等待
- 非阻塞: 为了完成一个功能发起调用,如果不具备完成功能的条件,则立即报错返回
同步与异步
并发模型和I/O模型都有同步/异步的概念:
-
并发模式中两者最主要的区别就是功能完成的流程是否是顺序化的,
-
I/O模型中两者最主要的区别是注册的是就绪事件还是完成事件,完成I/O操作的是用户程序自身还是内核系统。
-
同步: 功能完成的流程是顺序化的。注册就绪事件,读写事件由自身完成。
-
异步: 功能完成的流程是不确定的,注册完成事件,读写事件由内核完成。
下列五种IO模型,从前往后处理的效率逐渐增加,对资源的利用也增加充分,但是流程也越来越复杂。
阻塞IO
发起IO调用,如果不具备IO条件,则一直等待直到条件就绪。以 recvfrom
为例:(recvfrom
:UDP数据的读写函数)
- 优点: 流程以及代码实现都非常简单,任务顺序操作。
- 缺点: 任务处理效率较低,无法充分利用资源。
非阻塞IO
发起一个IO调用,如果不具备IO条件,则立即报错返回(无需等待),继续执行其他命令。通过一个循环来不断发起IO请求,直到条件就绪。
- 优点: 与阻塞IO相比较来说,利用了等待的时间去做了其他的事情,对资源的利用更加充分。
- 缺点: 与阻塞IO对比,IO调用需要循环发起,流程更加复杂。 并且如果IO条件就绪了,也要等待同一个调用上一轮循环结束后进入当前循环,才能进行处理,这就导致了 IO 不够实时。
非阻塞IO可以通过 fcntl函数
设置描述符状态来实现:
void SetNoBlock(int fd)
{int flag = fcntl(fd, F_GETFL, 0);flag |= O_NONBLOCK;fcntl(fd, F_SETFL, flag);
}
信号驱动IO
自定义一个IO就绪的信号,当IO就绪时就发出这个信号。在没有收到信号时,可以继续处理其他事情,一旦收到信号,就会中断当前操作,来优先处理IO事件。程序没有阻塞阶段。
- 优点: 相较于非阻塞IO,仍具有资源利用更加充分的优势;并且信号到来后就直接强行中断进行处理,更加实时。
- 缺点: 因为需要自定义信号,又要有主控流程也要有信号处理流程,并且还需要考虑信号是否可靠导致的事件丢失情况,流程会更加的复杂。
多路复用IO
用于对大量的IO事件进行监控,能够让用户只针对就绪了指定事件(可读、可写、异常) 的IO进行操作。只针对就绪的描述符进行操作,避免了阻塞,并且提高了效率。
值得一提的是,I/O复用函数对I/O本身的读写操作是阻塞的,他们能提高程序效率的原因在于具有同时监听多个I/O事件的能力。
在 Linux
下,操作系统提供了三种模型:select模型
、poll模型
、epoll模型
。
异步IO
IO处理的顺序不确定,整个IO的过程(等待 + 数据拷贝)由操作系统来完成而并非用户。程序没有阻塞阶段。
流程:
- 自定义一个IO完成信号
- 发起异步调用后返回,此时用户可以继续处理其他事情
- 系统进行IO事件的等待以及数据拷贝
- IO完成后通过信号通知进程IO
- 优点: 对资源的利用最为充分, 以最高的效率进行任务的处理。
- 缺点: 资源消耗较高, 流程最为复杂。