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);
        }
    }
}
相关文章
相关标签/搜索