IO模型

概念

同步/异步

同步:一个任务的完成需要依赖另外一个任务,只有被依赖的任务完成时才完成。同步是可靠的任务序列。

异步:不需要等待被异步的任务完成,只要自己完成调用就算完成。异步是不可靠的任务序列。

实现异步的三种方式:轮询状态通知回调

阻塞/非阻塞

阻塞:在调用结果返回前,不能处理其他任务。

非阻塞:在依赖任务完成前,可以处理其他任务。

组合

方式 场景
同步阻塞 调用者一直等待被依赖的任务完成。
同步非阻塞 调用者必须等待被依赖完成,但是期间可以做其他任务。
异步阻塞 调用者不必等待被依赖任务立刻完成,但是期间不能做其他事情。
异步非阻塞 调用者调用被依赖任务后,期间可以去做其他事情。

多路复用

内核一旦发现指定进程的一个或者多个IO条件准备读取,它就通知该进程。其最大优势是系统开销小,系统不必创建进程/线程。IO多路复用的实现方式主要有selectpollepoll三种,本质上是同步的,读写过程是阻塞的

实现

select

select 函数监视的文件描述符分3类,分别是writefdsreadfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符

select具体良好的跨平台性,但受限于最大文件描述符数量,64位linux平台默认为2048,可通过cat /proc/sys/fs/file-max文件查看。

select函数的具体案例为::select(maxFd + 1, &rfds, &wfds, &efds, tv)),如果tv参数为nullptr,则select函数会阻塞住,直到有某个文件描述符状态发生变化;如果设置为0则会立即返回;如果设置为其他时间则达到这个时间后超时返回。

DRA实际使用中,如果有定时任务,tv参数设置为下一个定时任务的执行时间,否则设置为nullptr

poll

pollselect没有本质区别,其基于链表来存储,因此没有最大连接数的限制。

缺点:

  1. 需要在poll或者select函数返回后遍历文件描述符来获取就绪的socket,如果有大量的连接但极少处于就绪状态则效率会比较低下。
  2. 大量的fd数组被整体从内核空间拷贝到用户空间,大量拷贝可能没有意义。

epoll

epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的拷贝只需一次。epoll支持水平触发LT(level trigger)边缘触发ET(edge trigger),最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fdepoll_wait便可以收到通知。

epoll使用过程中需要三个接口:

int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
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);

异步AIO

Linux提供了AIO库函数实现异步,但是用的很少。其他开源的库有libeventlibevlibuv等。

总结

简单说,selectpoll是轮询的方式,epoll是通知(callback)的方式。

高并发程序一般使用同步非阻塞方式而非多线程+同步阻塞方式,因为线程太多调度开销也会很大。

results matching ""

    No results matching ""