i/o复用:select、poll和epoll

    为了解决进程或线程阻塞到某个I/O系统调用,则就需要用到I/O复用技术,使进程不阻塞与某个特定的系统调用。select、poll和epoll是三种实现I/O复用的系统调用,用来监听用户感兴趣的文件描述符上是否有事件发生,一旦某个文件描述符就绪(一般是可读或者可写),能够通知应用程序进行相应的读写操作。但select、poll、epoll本质上还是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,这个过程是阻塞的,而异步I/O是不需要自己进行读写的,异步I/O的实现会把数据从内核拷贝到用户空间。

select: 在一定时间内,监听用户感兴趣的可读,可写,异常等事件。

   函数原型:int select(int nfds,structfd_set* readfds,struct fd_set* writefds,

                                      struct fd_set* execptfds,struct timeval*timeout);

    select通过三个事件集合分别保存可读、可写、异常事件对应的文件描述符集合,所以它只能处理这三种事件。而且这三个事件集是维护在用户态的,所以select调用需要向内核去拷贝事件集。内核会通过轮询的方式查找有事件发生的文件描述符,并在线修改事件集来通知用户哪些文件描述符已经就绪(可读可写异常)。所以每次调用select之前,都需要重置这三个文件描述符集合并再次拷贝到内核中。

    select成功时返回就绪的文件描述符个数;

    在超时时间内没有就绪的文件描述符返回0;

    select失败返回-1。

poll:在一定时间内,监听用户感兴趣的事件的文件描述符上是否有事件发生。

    函数原型:intpoll(struct pollfd* fds,nfds_t fds,int timeout);

    poll和select工作原理基本一致,区别在于poll可以监听任何类型的事件。poll将文件描述符、事件和内核的修改都定义在一个pollfd结构体中,用户态只维护一个事件集,所以所有事件都可以同一处理,而且因为poll用于内核修改的用于区分是否就绪的revents,不用去修改文件描述符fd和检测事件events,所以也不需要每次调用poll之前重置事件集。

epoll:

    函数原型:int epoll_create(int size);   //创建内核事件表

                     int epoll_ctl(int epollfd,int op,intfd,struct epoll_event* event);  //在事件表中添加、删除、修改事件

                     int epoll_wait(int epollfd,structepoll_event* events,int maxevents,int timeout)  //检查事件的发生

    epoll由三个函数组成,将功能区分开来。另外它把事件集合直接维护在内核中,无需从用户态拷贝到内核态。在内核中它采用的是回调的机制将有事件发生的文件描述符添加到就绪事件表中。

    成功时返回就绪的文件描述符的个数,同时返回就绪的文件描述符数组;

    失败时返回-1。

三者的比较:

    1、使用权限:select所使用的set_fd(事件集合)实际上是个整型数组,32bit的系统上关注的文件描述符个数最多有1024个。poll和epoll则可以达到系统允许打开的最大文件描述符个数(65535)。

    2、内核实现和工作效率:

        select和poll都采用的是轮询的方式,每次都要遍历这个文件描述符集合,才能知道哪些文件描述符就绪,因此内核中检测文件描述符的算法的时间复杂度是O(n);而epoll采用的是回调的方式,当文件描述符就绪时,就触发回调函数,回调函数将文件描述符及发生的额事件插入到内核就绪事件队列中,因此epoll在内核中检测就绪文件描述符算法的时间复杂度是O(1).

        所以当链接的活动比较频繁时,select和poll的效率比epoll高,因为epoll的回调函数调用过于频繁,所以epoll适用于链接较多,但是活动不频繁的情况。

    3、使用效率:

        从文件描述符的拷贝上来说,select和poll每次调用之前都需要从用户态向内核拷贝文件描述符集合;而epoll只需要在添加是拷贝一次即可。

        从返回值和查找效率上来说,select和poll返回的都只是就绪文件描述符的个数,所以还需要遍历一遍文件描述符集合来确定哪些文件描述符上有事件发生,查找效率O(n);而epoll返回的是就绪文件表舒服的个数以及就绪文件描述符的集合,不需要再去遍历,所以查找效率为O(1)。

      从工作模式上来说,select和poll只能工作在LT模式下。而epoll可以工作在高效的et模式下,并且,epoll还支持EPOLLONESHOT事件,从而进一步减少事件被触发的次数。

   

LT和ET模式:

    LT模式是当文件描述符上有事件发生(可读/可写),通知应用程序处理(读/写),如果应用程序没有处理,在下次调用时会再次通知应用程序。是epoll默认的工作方式。

    ET是在通知后,应用程序必须立即处理该事件,如果没有处理,再次调用epoll_wait时,也不会再通知了。减少了epoll事件被再次触发的次数,提高了效率。

    LT:水平触发,在大并发大流量的情况下,效率会很低。但是对代码编写要求低,不容易出现问题。

    ET:边缘触发,在大并发大流量的情况下,效率比LT要高很多。但对程序员代码编写要求很高,必须细致完全的处理每个事件,否则会引起事件丢失(没有处理)。

EPOLLONESHOT事件:

    即使epoll使用ET模式,一个socket上的事件仍然有可能被触发多次。这在并发程序中会引起一个问题:一个线程(或进程)在读取某个socket上的数据后开始处理这些数据,而在数据处理的过程中该socket又有新的数据可读(EPOLLIN再次被触发),此时另一个线程被唤醒还读取这些新的数据。这就出现了两个线程同时读取同一个socket的情况。而使用EPOLLONESHOT就可以实现一个socket在任意时刻都只被一个线程处理。

    对于注册了EPOLLONESHOT的socket,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非当我们使用epoll_ctl函数重新设置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket是,其他先生是不可能有机会操作该socket的。但当注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket的EPOLLONTSHOT事件,确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会去处理这个socket。

猜你喜欢

转载自blog.csdn.net/Dxiaoru/article/details/81072353
今日推荐