zoukankan      html  css  js  c++  java
  • epoll的基本概念和C10K问题

    epoll:Edge and Level Trigger Polling (epoll)

    边缘触发(edge trigger)和条件触发(level trigger)

    边缘触发是指每当状态变化时发生一个io事件,条件触发是只要满足条件就发
    生一个io事件。举个读socket的例子,假定经过长时间的沉默后,现在来了100个字
    节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程
    序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时条件触发的api会
    因为还有50个字节可读从而立即返回用户一个read ready notification。而边缘触发
    的api会因为可读这个状态没有发生变化而陷入长期等待。

    因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则
    这个socket就算废了。而使用条件触发的api时,如果应用程序不需要写就不要关注
    socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家
    常用的select就是属于条件触发这一类,以前本人就犯过长期关注socket写事件从而
    CPU 100%的毛病。

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

    from:http://www.rosoo.net/a/201103/11088.html

    另外:c10k.pdf

    摘要编写连接数巨大的高负载服务器程序时,经典的多线程模式和select模式都不再适用。应当抛弃它们,采用epoll/kqueue/dev_poll来捕获I/O事件。最后简要介绍了AIO


    由来:网络服务在处理数以万计的客户端连接时,往往出现效率低下甚至完全瘫痪,这被称为C10K问题。随着互联网的迅速发展,越来越多的网络服务开始面临C10K问题,作为大型网站的开发人员有必要对C10K问题有一定的了解。(本文的主要参考文献是 http://www.kegel.com/c10k.htmls。) C10K问题的最大特点是:设计不够良好的程序,其性能和连接数及机器性能的关系往往是非线性的。举个例子:如果没有考虑过C10K问题,一个经典的基于select的程序能在旧服务器上很好处理1000并发的吞吐量,它在2倍性能新服务器上往往处理不了并发2000的吞吐量。这是因为在策略不当时,大量操作的消耗和当前连接数n成线性相关。会导致单个任务的资源消耗和当前连接数的关系会是O(n)。而服务程序需要同时对数以万计的socket进行I/O处理,积累下来的资源消耗会相当可观,这显然会导致系统吞吐量不能和机器性能匹配。为解决这个问题,必须改变对连接提供服务的策略。


    基本策略:

    主要有两方面的策略:1.应用软件以何种方式和操作系统合作,获取I/O事件并调度多个socket上的I/O操作;2. 应用软件以何种方式处理任务和线程/进程的关系。前者主要有阻塞I/O、非阻塞I/O、异步I/O这3种方案,后者主要有每任务1进程、每任务1线程、单线程、多任务共享线程池以及一些更复杂的变种方案。常用的经典策略如下:
    1.         Serve one client with each thread/process, and use blocking I/O这是小程序和java常用的策略,对于交互式的长连接应用也是常见的选择(比如BBS)。这种策略很能难足高性能程序的需求,好处是实现极其简单, 容易嵌入复杂的交互逻辑。Apache、ftpd等都是这种工作模式。
    2.         Serve many clients with single thread, and use nonblocking I/O and readiness notification 这是经典模型,datapipe等程序都是如此实现的。优点在于实现较简单,方便移植,也能提供足够的性能;缺点在于无法充分利用多CPU的机器。尤其是程序本身没有复杂的业务逻辑时。
    3.         Serve many clients with each thread, and use nonblocking I/O and readiness notification 对经典模型2的简单改进,缺点是容易在多线程并发上出bug,甚至某些OS不支持多线程操作readiness notification
    4.         Serve many clients with each thread, and use asynchronous I/O 在有AI/O支持的OS上,能提供相当高的性能。不过AI/O编程模型和经典模型差别相当大,基本上很难写出一个框架同时支持AI/O和经典模型,降低了程序的可移植性。在Windows上,这基本上是唯一的可选方案。
    本文主要讨论模型2的细节,也就是在模型2下应用软件如何处理Socket I/O。
    select 与 poll
    最原始的同步阻塞 I/O 模型的典型流程如下:
    同步阻塞 I/O 模型的典型流程
    从应用程序的角度来说,read 调用会延续很长时间,应用程序需要相当多线程来解决
    并发访问问题。同步非阻塞I/O对此有所改进:
    经典的单线程服务器程序结构往往如下:
    do {
    Get Readiness Notification of all sockets
    Dispatch ready handles to corresponding handlers
    If (readable) {
    read the socket
    If (read done)
    Handler process the request
    }
    if (writable)
    write response
    if (nothing to do)
    close socket
    } while(True)
    非阻塞 I/O 模型的典型流程:
    异步阻塞 I/O 模型的典型流程
    其中关键的部分是readiness notification,找出哪一个socket上面发生了I/O事件。
    一般从教科书和例子程序中首先学到的是用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 > 1024的,那就只有祈求上帝保佑不会垮掉了。
    在ACE环境中,ACE_Select_Reactor针对这一点特别作了保护措施,但是还是有recv_n这样的函数间接的使用了select,这需要大家注意
    针对fd_set的问题,*nix提供了poll函数作为select的一个替代品。Poll的接口如下:
    int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
    第1个参数ufds是用户提供的一个pollfd数组,数组大小由用户自行决定,因此避免了FD_SETSIZE带来的麻烦。Ufds 是fd_set的一个完全替代品,从select到poll的移植很方便。到此为止,至少我们面对C10K,可以写出一个能work的程序了。
    然而Select和Poll在连接数增加时,性能急剧下降。这有两方面的原因:首先操作系统面对每次的 select/poll操作,都需要重新建立一个当前线程的关心事件列表,并把线程挂在这个复杂的等待队列上,这是相当耗时的。其次,应用软件在 select/poll返回后也需要对传入的句柄列表做一次扫描来dispatch,这也是很耗时的。这两件事都是和并发数相关,而I/O事件的密度也和 并发数相关,导致CPU占用率和并发数近似成O(n2)的关系
    epoll, kqueue, /dev/poll
    因为以上的原因,*nix的hacker们开发了epoll, kqueue, /dev/poll这3套利器来帮助大家,让我们跪拜三分钟来感谢这些大神。其中epoll是linux的方案,kqueue是freebsd的方案,/dev/poll是最古老的Solaris的方案,使用难度依次递增
    简单的说,这些api做了两件事:1.避免了每次调用select/poll时kernel分析参数建立事件等待结构的开销,kernel维护一个 长期的事件关注列表,应用程序通过句柄修改这个列表和捕获I/O事件。2.避免了select/poll返回后,应用程序扫描整个句柄表的开 销,Kernel直接返回具体的事件列表给应用程序
    在接触具体api之前,先了解一下边缘触发(edge trigger)和条件触发(level trigger)的概念。边缘触发是指每当状态变化时发生一个io事件,条件触发是只要满足条件就发生一个io事件。举个读socket的例子,假定经过 长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时条件触发的api会因为还有50个字节可读从 而立即返回用户一个read ready notification。而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。
    因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则这个socket就算废了。而使用条件触发的api 时,如果应用程序不需要写就不要关注socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家常用的select就是属于条件触发这一类,以前本人就犯过长期关注socket写事件从而CPU 100%的毛病。
    epoll的相关调用如下:
    int epoll_create(int 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)
    epoll_create创建kernel中的关注事件表,相当于创建fd_set。
    epoll_ctl修改这个表,相当于FD_SET等操作
    epoll_wait等待I/O事件发生,相当于select/poll函数
    epoll完全是select/poll的升级版,支持的事件完全一致。并且epoll同时支持边缘触发和条件触发,一般来讲边缘触发的性能要好一些。这里有个简单的例子:
    struct epoll_event ev, *events;
    int kdpfd = epoll_create(100);
    ev.events = EPOLLIN | EPOLLET; // 注意这个EPOLLET,指定了边缘触发
    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 = EPOLLIN | EPOLLET;
    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);
    }
    }
    简单介绍一下kqueue和/dev/poll
    kqueue是freebsd的宠儿,kqueue实际上是一个功能相当丰富的kernel事件队列,它不仅仅是select/poll的升级,而且可以处理signal、目录结构变化、进程等多种事件。
    Kqueue是边缘触发的
    /dev/poll是Solaris的产物,是这一系列高性能API中最早出现的。Kernel提供一个特殊的设备文件/dev/poll。应用程序打开这个文件得到操纵fd_set的句柄,通过写入pollfd来修改它,一个特殊ioctl调用用来替换select。由于出现的年代比较早,所以/dev/poll的接口现在看上去比较笨拙可笑
    C++开发:ACE 5.5以上版本提供了ACE_Dev_Poll_Reactor封装了epoll和/dev/poll两种api,需要分别在config.h中定义ACE_HAS_EPOLL和ACE_HAS_DEV_POLL来启用。Java开发: JDK 1.6的Selector提供了对epoll的支持JDK1.4提供了对/dev/poll的支持。只要选择足够高的JDK版本就行了
    异步I/O以及Windows
    和经典模型不同,异步I/O提供了另一种思路。和传统的同步I/O不同,异步I/O允许进程发起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍后或在接收到 I/O 操作完成的通知时,进程就可以检索 I/O 操作的结果。
    异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。异步I/O 模型的典型流程:
    异步非阻塞 I/O 模型的典型流程
    对于文件操作而言,AIO有一个附带的好处:应用程序将多个细碎的磁盘请求并发的提交给操作系统后,操作系统有机会对这些请求进行合并和重新排序,这对同步调用而言是不可能的——除非创建和请求数目同样多的线程。Linux Kernel 2.6提供了对AIO的有限支持——仅支持文件系统。libc也许能通过来线程来模拟socket的AIO,不过这对性能没意义。总的来说Linux的aio还不成熟,Windows对AIO的支持很好,有IOCP队列和IPCP回调两种方式,甚至提供了用户级异步调用APC功能。Windows下AIO是唯一可用的高性能方案,详情请参考MSDN。

    详见:http://www.kegel.com/c10k.html   The C10K problem (英文版)http://www.yuanma.org/data/2007/1203/article_2906.htm 网络编程之C10K问题

  • 相关阅读:
    Changing Icon File Of Push Button At Runtime In Oracle Forms 6i
    Set Font Properties On Mouse Hover Of Push Button And Text Items At Run time In Oracle Forms
    Change An Item Property Using Set_Item_Property In Oracle Forms
    Calling / Running a report in Oracle forms 10g / 11g
    Change Or Set Report Object Property At Run Time In Oracle Forms Using Set_Report_Object_Property Command
    Refresh / Updating a form screen in Oracle D2k Forms 6i
    Know How And When To Use System.Message_Level To Control Messages In Oracle Forms
    Perform Cut Copy Paste Operations Using Cut_Region Copy_Region Paste_Region Commands In Oracle Forms
    CHECKBOX_CHECKED built-in in Oracle D2k Forms
    Limiting To Select Only 5 Check Boxes Out Of Ten In Oracle Forms
  • 原文地址:https://www.cnblogs.com/dkblog/p/1997678.html
Copyright © 2011-2022 走看看