我们在进行服务端编程的时候, 不可避免的就是处理IO事件, 了解常见的IO模型, 配合线程模型, 则直接影响到我们的服务端编程范式, 常见的IO模型是下面几种:
Block IO
阻塞IO顾名思义: IO操作是阻塞的. 我们以read(2)
为例, 阻塞IO从操作发起到操作完成的时序图是这样的:
在调用返回之前, 进程都是阻塞的.这里面的阻塞有两种:
- 等待数据准备好的阻塞, 白白浪费资源, 无用阻塞
- 等待数据拷贝到缓冲区的阻塞, 不可避免的阻塞, 拷贝也是需要时间的
使用阻塞IO模型的好处在于: 编程思路简单, 在并发量不大, IO非密集的情况下能拥有不错的效率
NonBlock IO
非阻塞IO顾名思义: IO操作都是非阻塞的
非阻塞指的是: 调用方在操作不能立即完成的时候, 不会阻塞在进程上, 会立即返回(EAGAIN
或EWOULDBLOCK
); 以read(2)
为例, 非阻塞UI从操作发起到操作完成时序图是这样的:
单纯使用非阻塞IO就是瓜皮行为, 单单使用非阻塞IO并不能加速响应, 他会不断的轮询, 因为有可能数据在此次调用之后就准备好了, 如果不是不停的轮询, 则会造成延迟.
众所周知: 不断使用轮询, 会使得CPU忙等待, 还不如像阻塞IO一样, 起码不是忙等待 因为我们一般单独使用非阻塞IO都是while ((ret = ::write(...));
可是, 非阻塞IO在配合IO多路复用的时候, 便能大展身手, 发挥出相当高的性能.之后的Reactor
, Proactor
模型都是基于非阻塞IO的
IO Multiplexing
IO多路复用指是内核提供这样一种机制: 内核同时关注多个套接字(即连接), 当有时间发生的时候, 返回通知用户, 之后用户进行连接上的事件处理. 一般使用IO多路复用机制是这样的:
我们使用IO多路复用, 可以同时维护多个连接, 这就是解决C10K问题的较优解法之一. 具体IO多路复用的内容, 我们可以在之后编程模型的部分进行分析.
Signal-driven IO
信号驱动IO, 是基于OS的信号机制, 在有信号发生时, 捕获到相关的信号. 会调用专门的信号处理函数 以此完成IO操作
信号驱动IO最亮眼的地方在于: 注册信号驱动函数后, 在数据准备好之前, 进程继续执行 这是异步吗? 不是 但是, 我们可以在这段时间完成其他各类任务, 有信号触发区处理IO.
表面看上去, 信号驱动IO很风光, 然而实际上, 信号驱动IO, 仅仅可能在UDP
中使用 TCP
的网络通信程序, 完全不使用信号驱动IO. 其根本原因, 我们在后面信号部分会详述
Asynchronous IO
异步IO, 这是本质上和前4种IO都不相同的IO模型.
异步IO的根本是指: 执行异步IO操作, 返回, 当操作系统准备好数据, 并且拷贝到buffer后通知用户 前4种都是同步IO, 异步IO则是: 整个调用完成后才返回给用户
异步IO看上去很美好, 完全是一种新的IO模型, 有这样的优点:
- IO和计算可以重叠
- 充分利用DMA(直接内存存取)的特性
但是, 存在这这样几个问题:
- 异步IO属异步编程, 对于心智负担极重, 回调函数满天飞真的压力很大
- 基于上一条, 异步编程,极难DEBUG
- 类Unix平台下, 异步IO设施并不完善, Win下的异步IO还可以 关于异步IO的内容, 我们在编程模型部分, 会从
proactor
模型处出发.
信号驱动IO
和异步IO
都会收到内核的信号, 但是:
- 信号驱动IO的信号通知是: IO操作可以开始
- 异步IO的信号通知是: IO操作已经完成
一些概念
阻塞非阻塞的概念是针对调用方的:
- 阻塞: 调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回;
- 非阻塞: 不能立刻得到结果之前,该调用不会阻塞当前线程
同步, 异步的概念是针对被调用方的:
- 同步: 指被调用方得到最终结果之后才返回给调用方
- 异步: 被调用方先返回应答,然后再计算调用结果,计算完最终结果后再通知并返回给调用方。
比如说: 前四种都是同步IO因为都是数据准备好完成后才返回, 异步IO调用后即返回 (针对被调用方)
参考资料:
- UNP Chapter VI
- 即时通讯网