流 I\O操作 阻塞
流
- 可以进行I\O操作的内核对象
- 文件、管道、套接字……
- 流的入口:文件描述符(fd)
所有对流的读写操作,我们都可以称之为IO操作。
那么当一个流中再没有数据,read的时候,或者说 在流中已经写满了数据,再write,我们的IO操作就 会出现一种现象,就是阻塞现象
阻塞
非堵塞
阻塞等待: 空出大脑可以安心睡觉。(不占用CPU宝贵的时间片)
非阻塞,忙轮询: 浪费时间,浪费电话费,占用快递员时间(占用CPU,系统资源)
解决阻塞死等待的办法
阻塞死等待的缺点
办法一:非阻塞、忙轮询
办法二:select
select 代收员 比较懒,她只会告诉你快递到了,但是是谁到的,你需要挨个快递员问一遍
办法三:epoll(主角出场)
epoll特点好处:
- 与 select,poll 一样,但是增加了对I/O多路复用的技术
- 只关心“活跃”的链接,无需遍历全部描述符集合
- 能够处理大量的链接请求(系统可以打开的文件数目)
epoll API:
int epoll_create(int size); /** * @param epfd 用epoll_create所创建的epoll句柄 * @param op 表示对epoll监控描述符控制的动作 * * EPOLL_CTL_ADD(注册新的fd到epfd) * EPOLL_CTL_MOD(修改已经注册的fd的监听事件) * EPOLL_CTL_DEL(epfd删除一个fd) * * @param fd 需要监听的文件描述符 * * events : {EPOLLIN, EPOLLOUT, EPOLLPRI, EPOLLHUP, EPOLLET, EPOLLONESHOT} */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); struct epoll_event { __uint32_t events; // epoll 事件 epoll_data_t data; // 用户传递的数据 } typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; // 举例 struct epoll_event new_event; new_event.events = EPOLLIN | EPOLLOUT; new_event.data.fd = 5; epoll_ctl(epfd, EPOLL_CTL_ADD, 5, &new_event);
图解:
触发模式
水平触发 与 边缘触发
水平触发
水平触发优点:
水平触发的主要特点是,如果用户在监听epoll事件,当内核有事件的时候,会拷贝给用户态事件,但是如果用户只处理了一次,那么剩下没有处理的会在下一次epoll_wait再次返回该事件。这样如果用户永远不处理这个事件,就导致每次都会有该事件从内核到用户的拷贝,耗费性能,但是水平触发相对安全,最起码事件不会丢掉,除非用户处理完毕
边缘触发
边缘触发,相对跟水平触发相反,当内核有事件到达, 只会通知用户一次,至于用户处理还是不处理,以后将不会再通知。这样减少了拷贝过程,增加了性能,但是相对来说,如果用户马虎忘记处理,将会产生事件丢的情况。
摘自:https://blog.csdn.net/qq_35433716/article/details/85345907
延伸阅读:select、poll、epoll的区别