select,epool,pool解釋

內容主要來自搜狗實驗室技術交流文檔,linux

編寫連接數巨大的高負載服務器程序時,經典的多線程模式和select模式都再也不適合了.應該採用epool/kqueue/dev_pool來捕獲IO事件.api

------數組

問題的由來:服務器

C10K問題的最大特色就是:設計不夠良好的程序,其性能和連接數以及機器性能的關係是非線性的.多線程

例子:沒有考慮過C10k問題,一個經典的基於select的程序能在就服務器上很耗處理1000併發的吞吐量,可是在2倍性能新服務器上每每處理不了併發2000的吞吐量.併發

由於:大量操做的消耗和當前連接數n成線性相關.app

 

=-==================異步

基本策略:socket

主要有兩個方面的策略:async

1,應用軟件以何種方式和操做系統合做,獲取IO事件並調度多個socket上的IO操做;

2,應用軟件以何種方式處理任務和線程/進程的關係.

前者主要有阻塞IO,費阻塞IO,異步IO三種方式

後者主要有每任務1進程,每任務1線程,單線程,多任務共享線程池以及一些更復雜的變種方案.

經常使用的經典策略以下:

1,serve one client with each thread/process, and use blocking IO.,

2,serve many clients with single thread, and use nonblocking IO and readiness notification.

3,serve many clients with each thread, and use nonblocking IO and readliness notification

4,serve many clienets witch each thread, and use asynchronous IO.

接下倆主要介紹策略2.

 

=======================

經典的單線程服務器程序結構每每以下:

do{
    get readiness notification of all sockets
    dispatch ready handles to corresponding handlers
    if(readable){
        read the socketsif
        if(read done){
            handler process the request

        }
    }
    if(writable){
        write response
    }
    if(nothing to do){
        close socket
    }
}while(True)

其中關鍵的部分就是readiness notification,找出哪個socket上面發生了IO事件.

通常從教科書和例子程序中會學到select來實現,

select函數的定義:

int select(int n,fd_set *rd_fds,fd_set *wr_fds, fd_set *ex_fds,struct timeval *timeout);

select用到了fd_set結構,從man page裏能夠看到fd_set能容納的句柄和FD_SETSIZE相關.實際上fd_set在*nix下是一個bit標誌數組,每一個bit表示對應下標的fd是否是在fd_set中.  fd_set只能容納編號小於FD_SETSIZE的那些句柄.

----

FD_SETSIZE默認是1024,若是向fd_set中放入過大的句柄,數組越界之後程序就會垮掉.系統默認限制了一個進程最大的句柄號小於 1024,可是能夠經過ulimit  -n命令或者setrlimit函數來擴大這一限制.若是不幸一個程序在FD_SETSIZE=1024的環境下編譯,運行時又遇到ulimit --n>1014的,會出現未定義錯誤.

-----

針對fd_set的問題,*nix提供了poll函數做爲select的一個替代品,

int poll(struct poollfd *ufds, unsigned int nfds  ,int timeout);

第一個參數ufds是用戶提供的一個pollfd數組,數組大小由用戶自行決定.所以避免了FD_SETSIZE帶來的麻煩.

ufds是fd_set的一個徹底替代品,從select到poll的一直很方便,到此咱們面對C10k,能夠寫出一個能work的程序了.

------

可是select/poll在連接數增長時,性能急劇降低.

由於:

1,os面對每次的select/poll操做,都須要從新創建一個當前線程的關心事件列表,並把線程掛到這個複雜的等待隊列上,耗時.

2,app在select/poll返回後,也須要堆傳入的句柄列表作一次掃描來dispatch,耗時.

這兩件事,都是和併發數相關,而事件IO的密度也和併發數相關,致使cpu佔用率和併發數近似成O(n2)的關係.

-----------epoll出廠了.

由於以上緣由,*nix的開發者開發了epoll,kqueue,/dev/poll這3套利器來幫助你們,

epoll是linux的方案,kqueue是freebsd方案,/dev/poll是最古老的solaris方案,使用難度一次遞增.

爲何這些api是優化方案:

1,避免了每次調用select/poll時kernel分析參數創建事件等待結構的開銷,kernel維護一個長期的事件關注列表,

應用程序經過句柄修改這個列表和捕獲IO事件

2,避免了select/poll返回後,app掃描整個句柄表的開銷,kernel直接返回具體的事件列表爲app.

---先了解

邊緣觸發(edge trigger):指每當狀態變化時發生一個IO事件

和條件觸發(level trigger):只要知足條件就發生一個IO事件

舉個例子:讀socket,假設進過長時間沉默後,來了100個字節,這是不管邊緣觸發/條件觸發都會產生一個read ready notification通知應用程序可讀.  app先讀了50bytes,從新調用api等待io,這時條件觸發的api由於還有50bytes刻度可當即返回用戶一個read ready notification.  而邊緣觸發的api由於這個可讀狀態沒變陷入長期等待.

 

使用邊緣觸發的api時,注意每次要讀到socket返回EWOULDBLOCK爲止,不然這個socket就廢了.

而條件觸發的api,若是app不須要寫就不要關注socket可寫的事件,不然會無限次的當即返回一個write ready notification.

條件觸發比較經常使用.

int epoll_create(int size);
int epool_ctl(int epfd,int op,int fd, struct epoll_event *event);
int epool_wait(int epfd,struct epoll_event *events, int maxevents,int timeout);

epoll_create 建立kernel中的關注事件表,至關於建立fd_set

epoll_ctl 修改這個表,至關於FD_SET等操做

epoll_wait 等待IO事件發生,至關於select/poll函數

epoll徹底是select/poll的升級版,支持的事件一致.而且epoll同時支持條件/邊緣觸發(後者較好).

struct epoll_event ev,*events;
int kdpdf = epoll_create(100);
ev.events = EPOOL|EPOLLET;//edge trigger
ev.data.fd = listener;
epoll_ctl(kdpfd,EPOLL_CTL_ADD,listener,&ev);
for(;;){
    nfds = epoll_wait(kdpfd,events,maxevents,-1);
    for(n = 0;n<nfds;n++){
        if(events[n].data.fd == listener){
            client = accept(listener,(struct sockaddr *)&local,&addrlen);
            if(client <0){
                perror("accept");
                continue;
            }
            setnonblocking(client);
            ev.events = EPOOLIN|EPOOLET;
            ev.data.fd = client;
            if(epoll_ctl(kdpfd,EPOLL_CTL_ADD,client,&ev)<0){
                fprintf(stderr,"epoll set insertion error: fd=%d0",client)
                return -1;
            }
        }else{
            do_use_fd(events[n].data.fd);
        }
    }
}
相關文章
相關標籤/搜索