关于:5种I/O模型
关于
【讨论的背景是 Unix/Linux 环境下的 network IO】
I/O(Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作。
- I/O有:内存I/O、网络I/O 和 磁盘I/O 三种。(通常说的I/O指的是后两者)
LINUX 中进程无法直接操作 I/O 设备,其必须通过系统调用请求 kernel 来协助完成 I/O 动作;内核会为每个 I/O 设备维护一个缓冲区。
- 对于一个输入操作来说,进程I/O系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备I/O一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。
所以,对于一个 network I/O,它会涉及到两个系统对象:调用I/O的process(or thread),系统内核(kernel)。
当一个 I/O 操作发生时,kernel 中会经历两个阶段:
- 等待数据准备(Waiting for the data to be ready);
- 将数据从内核拷贝到进程中(Copying the data from the kernel to the process);
记住这两点很重要,因为这些 IO Model 的区别就是在两个阶段上各有不同的情况。
5种I/O模型
《UNIX网络编程》:5 种I/O模型分别是阻塞I/O模型、非阻塞I/O模型、I/O复用模型、信号驱动的I/O模型、异步I/O模型;
- 其中,前 4 种为“同步I/O操作”,只有 异步I/O模型 是“异步I/O操作”。
阻塞I/O模型(blocking IO)
进程阻塞于 recvfrom 调用,直到返回成功指示。(进程阻塞直到 kernel 中两个阶段结束)
典型应用:
- 阻塞socket;【默认情况下所有的 socket 都是 blocking】
- Java BI/O;
特点:
- 进程阻塞挂起不消耗CPU资源,及时响应每个操作;
- 实现难度低、开发应用较容易;
- 适用并发量小的网络应用开发;
- 不适用并发量大的应用:因为一个请求I/O会阻塞进程,所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。
非阻塞I/O模型(nonblocking IO)
进程反复调用 recvfrom,并等待返回成功指示。(进程反复调用 recvfrom 而不阻塞)
进程发起 I/O 系统调用后:
- 如果内核缓冲区没有数据,需要到 I/O 设备中读取,进程返回一个错误而不会被阻塞;
- 如果内核缓冲区有数据,内核就会把数据返回进程。
典型应用:
- socket(linux下,可以通过设置socket使其变为non-blocking)
特点:
- 进程轮询(重复)调用,消耗CPU的资源;
- 实现难度低、开发应用相对阻塞I/O模式较难;
- 适用并发量较小、且不需要及时响应的网络应用开发;
I/O复用模型(IO multiplexing)
I/O复用模型(也称:“event driven IO”,事件驱动I/O),其好处就在于单个process就可以同时处理多个网络连接的 IO。
基本原理:【select/epoll 这个 function 会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程】
等待数据准备: 用户进程调用 select,整个进程被block。 同时,kernel 会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。 将数据从内核拷贝到进程中: 用户进程再调用 recvfrom(read 操作),将数据从kernel拷贝到用户进程。
- 使用到两个系统调用:(select 和 recvfrom)
- select:轮询socket,等待数据;
- recvfrom:将数据从内核拷贝到进程中;
- 用户进程阻塞两次:
- 进程阻塞于 select 调用,等待某个socket可读;(阻塞于 select 调用)
- 进程阻塞于 数据拷贝到应用缓冲区;(阻塞于 recvfrom 调用)
注意:
- 其优势,并不在于性能(连接数不高时,并不一定比“阻塞I/O”更好),而是在于同时处理多个connection;
典型应用:
- select、poll、epoll三种多路I/O复用方案;(nginx都可以选择使用这三个方案)
- Java NIO;
特点:
- 专一进程解决多个进程I/O的阻塞问题,性能好;Reactor模式;
- 实现、开发应用难度较大;
- 适用高并发服务应用开发:一个进程(线程)响应多个请求;
select、poll、epoll
Linux 中I/O复用的实现方式主要有:
- select:注册I/O、阻塞扫描。
- 监听的 I/O 最大连接数不能多于 FD_SIZE;
- poll:原理和 select 相似,没有数量限制,但I/O数量大扫描线性性能下降;
- epoll :事件驱动不阻塞,mmap 实现内核与用户空间的消息传递,数量很大。
- Linux 2.6 后内核支持;
- 【Redis 的单线程模型中,“文件事件处理器”就采用 epoll 实现 多路I/O复用】
信号驱动的I/O模型(signal driven IO)
当进程发起一个 I/O 操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用 I/O 读取数据。
- 进程阻塞在第二个阶段(数据拷贝到应用缓冲区期间);
典型应用:
- 【好像应用比较少】
特点:
- 回调机制,实现、开发应用难度大;
异步I/O模型(asynchronous IO)
用户线程与内核异步执行:
- 用户线程:发起一个 aio_read 后(立刻得到内核返回),继续执行其他逻辑;
- 内核:
- 收到一个 aio_read 后立刻返回。
- 然后,两个阶段(等待数据准备完成,将数据拷贝到用户内存)完成之后,给用户进程发送一个 signal。
典型应用:
- JAVA7 AIO;【???】
- 高性能服务器应用;
特点:
- 不阻塞,数据一步到位;Proactor模式;
- 需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;
- 实现、开发应用难度大;
- 非常适合高性能高并发应用;
I/O模型比较
I/O:blocking vs non-blocking、synchronous vs asynchronous
blocking vs non-blocking
blocking:用户进程会阻塞直到整个 I/O 结束;(阻塞I/O模型) non-blocking:在kernel还准备数据的情况下会立刻返回;(非阻塞I/O模型)
- 但,阻塞I/O模型、I/O复用模型、信号驱动的I/O模型,都存在用户线程阻塞的情况。
synchronous vs asynchronous
对于 synchronous IO 和 asynchronous IO,Stevens给出的定义(其实是POSIX的定义)如下:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes; An asynchronous I/O operation does not cause the requesting process to be blocked;
- 此处,“I/O operation”是指真实的IO操作,就是例子中的recvfrom这个system call。
对于“非阻塞I/O模型”(non-blocking IO):在执行 recvfrom 这个system call的时候,如果 kernel 的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom 会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。【见上一节:“I/O模型比较”图】
抛却以上说法,其实,二者重点关注在“用户线程发送I/O之后,是否关注内核操作”,以此作为区分:
- 只有“异步I/O模型”是异步I/O,其余“阻塞I/O模型”、“非阻塞I/O模型”、“I/O复用模型”、“信号驱动的I/O模型”都属于同步I/O。