select poll epoll三者之間的比較

1、概述linux


 

  說到Linux下的IO複用,系統提供了三個系統調用,分別是select poll epoll。那麼這三者之間有什麼不一樣呢,何時使用三個之間的其中一個呢?編程

  下面,我將從系統調用原型來分析其中的不一樣。windows

 

2、系統接口原型數組


 

  1. selectsocket

        #include <sys/select.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);
        

 

  2. pollide

        #include <poll.h>

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);
     int ppoll(struct pollfd *fds, nfds_t nfds,
               const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

 

  3. epoll函數

       #include <sys/epoll.h>

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
       int epoll_pwait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout,
                      const sigset_t *sigmask);

3、參數對比性能


1. select測試

  • select的第一個參數nfdsfdset集合中最大描述符值加1fdset是一個位數組,其大小限制爲__FD_SETSIZE1024),位數組的每一位表明其對應的描述符是否須要被檢查;
  • select的第二三四個參數表示須要關注讀、寫、錯誤事件的文件描述符位數組,這些參數既是輸入參數也是輸出參數,可能會被內核修改用於標示哪些描述符上發生了關注的事件。因此每次調用select前都須要從新初始化fdset
  • timeout參數爲超時時間,該結構會被內核修改,其值爲超時剩餘的時間。
  • select對應於內核中的sys_select調用,sys_select首先將第二三四個參數指向的fd_set拷貝到內核,而後對每一個被SET的描述符調用進行poll,並記錄在臨時結果中(fdset),若是有事件發生,select會將臨時結果寫到用戶空間並返回;當輪詢一遍後沒有任何事件發生時,若是指定了超時時間,則select會睡眠到超時,睡眠結束後再進行一次輪詢,並將臨時結果寫到用戶空間,而後返回。
  • select返回後,須要逐一檢查關注的描述符是否被SET(事件是否發生)。

2. poll優化

  •   pollselect不一樣,經過一個pollfd數組向內核傳遞須要關注的事件,故沒有描述符個數的限制,pollfd中的events字段和revents分別用於標示關注的事件和發生的事件,故pollfd數組只須要被初始化一次。
  • poll的實現機制與select相似,其對應內核中的sys_poll,只不過poll向內核傳遞pollfd數組,而後對pollfd中的每一個描述符進行poll,相比處理fdset來講,poll效率更高。
  • poll返回後,須要對pollfd中的每一個元素檢查其revents值,來得指事件是否發生。

  poll事件類型

事件 描述 是否可做爲輸入 是否可做爲輸出
POLLIN 數據(包括普通數據和優先數據)
POLLRDNORM 普通數據可讀
POLLRDBAND 優先級帶數據可讀(Linux不支持)
POLLPRI 高優先級數據可讀,好比TCP帶外數據
POLLOUT 數據(包括普通數據和優先數據)可寫
POLLWRNORM 普通數據可寫
POLLWRBAND 優先級帶數據可寫
POLLRDHUP TCP鏈接被對方關閉,或者對方關閉了寫操做。它由GNU引入
POLLERR 錯誤
POLLHUP 掛起。好比管道的寫端被關閉後,讀端描述符上將收到POLLHUP事件
POLLNVAL 文件描述符沒有打開

 

3. epoll

  • epoll是Linux特有的I/O複用函數。它在實現上與select、poll有很大的差別。首先,epoll使用一組函數來完成任務,而不是單個函數
  • 其次,epoll把用戶關心的文件描述符上的事件放在內核裏的一個事件表中,從而無需像select和poll那樣每次調用都要重複傳入文件的事件放在內核裏的一個事件表中。但epoll須要使用一個額外的文件描述符,來惟一標識內核中的這個事件表;
  • epoll經過epoll_create建立一個用於epoll輪詢的描述符,經過epoll_ctl添加/修改/刪除事件,經過epoll_wait檢查事件,epoll_wait第二個參數用於存放結果
  • epollselectpoll不一樣,首先,其不用每次調用都向內核拷貝事件描述信息,在第一次調用後,事件信息就會與對應的epoll描述符關聯起來。另外epoll不是經過輪詢,而是經過在等待的描述符上註冊回調函數,當事件發生時,回調函數負責把發生的事件存儲在就緒事件鏈表中,最後寫到用戶空間。
  • epoll返回後,該參數指向的緩衝區中即爲發生的事件,對緩衝區中每一個元素進行處理便可,而不須要像pollselect那樣進行輪詢檢查。

 

4、性能對比


 

  selectpoll的內部實現機制類似,性能差異主要在於向內核傳遞參數以及對fdset的位操做上,另外,select存在描述符數的硬限制,不能處理很大的描述符集合

  這裏主要考察pollepoll在不一樣大小描述符集合的狀況下性能的差別。

 

  測試程序會統計在不一樣的文件描述符集合的狀況下,1s pollepoll調用的次數。

  統計結果以下,從結果能夠看出,poll而言,每秒鐘內的系統調用數目雖集合增大而很快下降,而epoll基本保持不變,具備很好的擴展性

 

描述符集合大小

poll

epoll

1

331598

258604

10

330648

297033

100

91199

288784

1000

27411

296357

5000

5943

288671

10000

2893

292397

25000

1041

285905

50000

536

293033

100000

224

285825

 

5、鏈接數

 


 

  我本人也曾經在項目中用過select和epoll,對於select,感觸最深的是linux下select最大數目限制(windows 下彷佛沒有限制),每一個進程的select最多能處理FD_SETSIZE個FD(文件句柄),若是要處理超過1024個句柄,只能採用多進程了。
  常見的使用select的多進程模型是這樣的:

  一個進程專門accept,成功後將fd經過UNIX socket傳遞給子進程處理,父進程能夠根據子進程負載分派。

  曾經用過1個父進程 + 4個子進程 承載了超過4000個的負載。
  這種模型在咱們當時的業務運行的很是好。

 

  epoll在鏈接數方面沒有限制,固然可能須要用戶調用API重現設置進程的資源限制。

 

6、相同點


  • 都能同時監聽多個文件描述符;
  • 它們將等待由timeout參數指定的超時時間,直到一個或者多個文件描述符上有事件發生時返回,返回值是就緒的文件描述符的數量;

 

7、不一樣點


 

  對於select:

  1. 只能經過三個結構體參數處理三種事件,分別是:可讀、可寫和異常事件,而不能處理更多的事件;

  2. 這三個參數既是輸入參數,也是輸出參數,所以,在每次調用select以前,都得對fd_set進行重置;

  對於poll:

  1. 將文件描述符和事件關聯在一塊兒,任何事件都被統一處理,從而使得編程接口簡潔很多;

  2. 內核改變的變量是revents,而不是events,所以,調用以前不須要再重置;

 

  因爲每次select和poll調用都返回整個用戶註冊的事件集合(其中包括就緒的和未就緒的),因此應用程序索引就緒文件描述符的時間複雜度爲O(n)。

  而epoll採用與select和poll徹底不一樣的方式來管理用戶註冊的事件。

 

8、poll和epoll在使用上的差異


 

/*poll example*/
/*如何索引poll返回的就緒文件描述符*/
int ret = poll(fds, MAX_EVENT_NUMBER, -1);
/*必須遍歷全部已註冊文件描述符並找到其中的就緒者(固然,能夠利用ret來稍做優化)*/
for(int i = 0; i < MAX_EVENT_NUMBER; ++i)
{
    if(fds[i].revents & POLLIN)
    {
    int sockfd = fds[i].fd;
        //deal with sockfd.
    }
}


/*epoll example*/
int epfd = epoll_create(MAXSIZE);

struct epoll_event ev,events[5000];
//設置與要處理的事件相關的文件描述符
ev.data.fd=listenfd;
//設置要處理的事件類型
ev.events=EPOLLIN|EPOLLET;
//註冊epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

int nfds = epoll_wait(epfd,events,6000,-1);
//處理所發生的全部事件     
for(int i = 0; i< nfds; ++i)
{
    //new accept.
    if(events[i].data.fd == listenfd)
    {
        printf("listen=%d\n",events[i].data.fd);
        connfd = accept(listenfd,(sockaddr *)(&clientaddr), &clilen);
        if(connfd<0)
        {
            perror("connfd<0");
            exit(1);
        }            
        setnonblocking(connfd);                 
        char *str = inet_ntoa(clientaddr.sin_addr);
        std::cout<<"connec_ from >>"<<str<<"  "<<connfd<<std::endl;

        //設置用於讀操做的文件描述符
        ev.data.fd = connfd;

        //設置用於注測的讀操做事件
        //ev.data.ptr = NULL;
        ev.events = EPOLLIN|EPOLLET;

        //註冊ev
        epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
        ev.data.fd = listenfd;

        //設置要處理的事件類型
        ev.events=EPOLLIN|EPOLLET;
       
        //註冊epoll事件
        epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev);
        continue;
    }
    else if(events[i].events & EPOLLIN)
    {
        num1++;
        //fprintf(stderr,"reading! %d\n",num1);               
        if( (sockfd = events[i].data.fd) <= 0)
        {
            num1--;
            continue;
        }
        
        new_task = NULL;
        while(new_task == NULL)
            new_task = new task();
                                       
        new_task->fd = sockfd;
        new_task->next=NULL;
                                                
        //fprintf(stderr,"sockfd %d",sockfd);
        //添加新的讀任務
        pthread_mutex_lock(&mutex);
        if(readhead == NULL)
        {
            readhead = new_task;
            readtail = new_task;
        }   
        else
        {   
            readtail->next=new_task;
            readtail=new_task;
        }   
        //喚醒全部等待cond1條件的線程
        pthread_cond_broadcast(&cond1);
        pthread_mutex_unlock(&mutex);  
        continue;
    }
    else if(events.events & EPOLLOUT)
    {   
        //fprintf(stderr,"EPOLLOUT");
        num++;
        rdata=(struct user_data *)events[i].data.ptr;
        sockfd =rdata->fd;
        if(old == sockfd)
        {
            fprintf(stderr,"repreted sockfd=%d\n",sockfd);
            //exit(1);
        }
        old=sockfd;       
        //fprintf(stderr,"write  %d\n",num);
        int size=write(sockfd, rdata->line, rdata->n_size);
        //fprintf(stderr,"write=%d delete rdata\n",size);
        fprintf(stderr,"addr=%x fdwrite=%d size=%d\n",rdata,rdata->fd,size);
        
        if(rdata!=NULL)//主要問題致使delete重複相同對象 events返回對象相同
        {
            delete rdata;
            rdata=NULL;
        }
        
        //設置用於讀操做的文件描述符
        //fprintf(stderr,"after delete rdata\n");
        ev.data.fd=sockfd;
                             
        //設置用於注測的讀操做事件
        ev.events=EPOLLIN|EPOLLET;

        //修改sockfd上要處理的事件爲EPOLIN
        res = epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
        
        while(res==-1)
        {
            //fprintf(stderr,"out error");
            exit(1);
        }
        //fprintf(stderr,"out EPOLLOUT\n");
        continue;
    }
    else if(events.events&(EPOLLHUP|EPOLLERR))
    {
        //fprintf(stderr,"EPPOLLERR\n");
        int fd=events.data.fd;
        if(fd>6000)
        {
            fd=((struct user_data*)(events.data.ptr))->fd;
        }
        //設置用於注測的讀操做事件
        ev.data.fd=fd;
        ev.events=EPOLLIN|EPOLLET|EPOLLOUT;

        //修改sockfd上要處理的事件爲EPOLIN
        epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);
    }
}
相關文章
相關標籤/搜索