目录
前言
一、阻塞和非阻塞的区别
二、同步与异步的区别
三、Linux的五种I/O模型及特点
同步I/O
异步I/O
比较结果:
四、select、poll、epoll之间的区别
select:
poll:
epoll:
对比分析
一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪”和“数据读写”,数据就绪阶段分为阻塞和非阻塞,表现的结果是:阻塞当前线程或是直接返回。
数据就绪:根据系统IO操作的就绪状态
阻塞
非阻塞
数据读写:根据应用程序和内核的交互方式
同步
异步
1.阻塞是指在发起一个调用之后,在消息返回之前,当前进程/线程会被挂起,知道消息返回,当前进程/线程才会被激活;
2.非阻塞是指发起一个调用后,不会阻塞当前进程/线程,而会立即返回。
这里的阻塞/非阻塞强调的是等待消息时的状态。
1.同步是指在发起一个调用后,调用者需要一直等待调用结果的通知,才能进行后续的操作;
同步表示A向B请求一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞)
2.异步是指在发起一个调用后,调用者不能立即得到一个调用结果的返回,需要被调用者通过状态、通知和回调来通知调用者。
异步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生后的通知方式,A就可以处理其他逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。
陈硕大佬原话:在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用了特殊的API才是异步IO.
1.阻塞I/O(blocking):进程保持阻塞状态,直到数据拷贝完成
2.非阻塞I/O(non-blocking):轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理;需要注意的是,拷贝数据的过程中,进程依然是阻塞装填;
3.IO多路复用(IO multiplexing):进程调用select、poll、epoll函数,保持阻塞状态,但与阻塞I/O不同的是,这些函数可以同时处理多个I/O;
4.信号驱动(signal-driven):首先建立一个信号处理函数,进程继续运行并不阻塞,当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中处理数据;
注:信号驱动在内核第一阶段是异步,第二阶段是同步,与非阻塞IO的区别在于他提供信息通知机制,不需要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。
5.异步I/O(asynchronous):在发起一个调用之后,调用者不能立即通过得到调用结果的返回,需要调用者通过状态、通知和回调来通知调用者。
同步:等待数据阶段处理结果不同,数据从内核空间拷贝到用户空间阶段处理相同;
异步:与同步最大的区别就是数据从内核空间拷贝到用户空间是内核完成的,而非同步中用户完成。
select本质是上是通过设置和轮询fd_set来检查是否有就绪的文件描述符,其缺点在于:
1.单个进程可监视的文件描述符数量较少,在32位机器上默认为1024个,在64位机器上默认为2048个;
2.每次调用select都需要吧fd_set从用户空间拷贝到内核空间,文件描述符较多时开销较大;
3.每次调用select都需要线性扫描fd_set,文件描述符较多时开销较大。
poll与select相识,不同之处在于poll使用pollfd链表结构保存文件描述符,因此与select相比,没有文件描述符数量限制。
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
对文件,描述符的操作有两种模式:
1.水平触发
当epoll_wait检测到描述符事件 发生并将此事件通知应用程序,应用程序可以不立即处理该事件,下次调用epoll_wait时,将会再次响应应用程序并通知此事件;
2.边沿触发
当epoll_wait检测到描述符事件并将此事件通知应用程序,应用程序必须立即处理该事件,如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件;
注意:表面上看epoll的性能最好,但是连接数量较少并且都十分活跃的情况下,select和poll的性能可能较好,因为epoll的通知机制需要使用回调函数。
#include
int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};