`
sunqi
  • 浏览: 230844 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

IO复用与并发编程(转)

 
阅读更多

对于服务器的并发处理能力,我们需要的是:每一毫秒服务器都能及时处理这一毫秒内收到的数百个不同TCP连接上的报文,与此同时,可能服务器上还有数以十万计的最近几秒没有收发任何报文的相对不活跃连接。同时处理多个并行发生事件的连接,简称为并发;同时处理万计、十万计的连接,则是高并发。服务器的并发编程所追求的就是处理的并发连接数目无限大,同时维持着高效率使用CPU等资源,直至物理资源首先耗尽。

 
并发编程有很多种实现模型,最简单的就是与“线程”捆绑,1个线程处理1个连接的全部生命周期。优点:这个模型足够简单,它可以实现复杂的业务场景,同时,线程个数是可以远大于CPU个数的。然而,线程个数又不是可以无限增大的,为什么呢?因为线程什么时候执行是由操作系统内核调度算法决定的,调度算法并不会考虑某个线程可能只是为了一个连接服务的,它会做大一统的玩法:时间片到了就执行一下,哪怕这个线程一执行就会不得不继续睡眠。这样来回的唤醒、睡眠线程在次数不多的情况下,是廉价的,但如果操作系统的线程总数很多时,它就是昂贵的(被放大了),因为这种技术性的调度损耗会影响到线程上执行的业务代码的时间。举个例子,这时大部分拥有不活跃连接的线程就像我们的国企,它们执行效率太低了,它总是唤醒就睡眠在做无用功,而它唤醒争到CPU资源的同时,就意味着处理活跃连接的民企线程减少获得了CPU的机会,CPU是核心竞争力,它的无效率进而影响了GDP总吞吐量。我们所追求的是并发处理数十万连接,当几千个线程出现时,系统的执行效率就已经无法满足高并发了。
 
对高并发编程,目前只有一种模型,也是本质上唯一有效的玩法。
从这个系列的前4篇文章可知,连接上的消息处理,可以分为两个阶段:等待消息准备好、消息处理。当使用默认的阻塞套接字时(例如上面提到的1个线程捆绑处理1个连接),往往是把这两个阶段合而为一,这样操作套接字的代码所在的线程就得睡眠来等待消息准备好,这导致了高并发下线程会频繁的睡眠、唤醒,从而影响了CPU的使用效率。
 
高并发编程方法当然就是把两个阶段分开处理。即,等待消息准备好的代码段,与处理消息的代码段是分离的。当然,这也要求套接字必须是非阻塞的,否则,处理消息的代码段很容易导致条件不满足时,所在线程又进入了睡眠等待阶段。那么问题来了,等待消息准备好这个阶段怎么实现?它毕竟还是等待,这意味着线程还是要睡眠的!解决办法就是,主动查询,或者让1个线程为所有连接而等待!
这就是IO多路复用了。多路复用就是处理等待消息准备好这件事的,但它可以同时处理多个连接!它也可以“等待”,所以它也可能导致线程睡眠,然而这不要紧,因为它一对多、它可以监控所有连接。这样,当我们的线程被唤醒执行时,就一定是有一些连接准备好被我们的代码执行了,这是有效率的!没有那么多个线程都在争抢处理“等待消息准备好”阶段,整个世界终于清净了!
 
多路复用有很多种实现,在linux上,2.4内核前主要是select和poll,现在主流是epoll,它们的使用方法似乎很不同,但本质是一样的。
效率却也不同,这也是epoll完全替代了select的原因。
 
简单的谈下epoll为何会替代select。
前面提到过,高并发的核心解决方案是1个线程处理所有连接的“等待消息准备好”,这一点上epoll和select是无争议的。但select预估错误了一件事,就像我们开篇所说,当数十万并发连接存在时,可能每一毫秒只有数百个活跃的连接,同时其余数十万连接在这一毫秒是非活跃的。select的使用方法是这样的:
返回的活跃连接 ==select(全部待监控的连接)
什么时候会调用select方法呢?在你认为需要找出有报文到达的活跃连接时,就应该调用。所以,调用select在高并发时是会被频繁调用的。这样,这个频繁调用的方法就很有必要看看它是否有效率,因为,它的轻微效率损失都会被“频繁”二字所放大。它有效率损失吗?显而易见,全部待监控连接是数以十万计的,返回的只是数百个活跃连接,这本身就是无效率的表现。被放大后就会发现,处理并发上万个连接时,select就完全力不从心了。
 
看几个图。当并发连接为一千以下,select的执行次数不算频繁,与epoll似乎并无多少差距:
alt
然而,并发数一旦上去,select的缺点被“执行频繁”无限放大了,且并发数越多越明显:
alt
再来说说epoll是如何解决的。它很聪明的用了3个方法来实现select方法要做的事:
新建的epoll描述符==epoll_create()
epoll_ctrl(epoll描述符,添加或者删除所有待监控的连接)
返回的活跃连接 ==epoll_wait( epoll描述符 )
这么做的好处主要是:分清了频繁调用和不频繁调用的操作。例如,epoll_ctrl是不太频繁调用的,而epoll_wait是非常频繁调用的。这时,epoll_wait却几乎没有入参,这比select的效率高出一大截,而且,它也不会随着并发连接的增加使得入参越发多起来,导致内核执行效率下降。
 
epoll是怎么实现的呢?其实很简单,从这3个方法就可以看出,它比select聪明的避免了每次频繁调用“哪些连接已经处在消息准备好阶段”的 epoll_wait时,是不需要把所有待监控连接传入的。这意味着,它在内核态维护了一个数据结构保存着所有待监控的连接。这个数据结构就是一棵红黑树,它的结点的增加、减少是通过epoll_ctrl来完成的。用我在《深入理解Nginx》第8章中所画的图来看,它是非常简单的:
alt
 
图中左下方的红黑树由所有待监控的连接构成。左上方的链表,同是目前所有活跃的连接。于是,epoll_wait执行时只是检查左上方的链表,并返回左上方链表中的连接给用户。这样,epoll_wait的执行效率能不高吗?
 
最后,再看看epoll提供的2种玩法ET和LT,即翻译过来的边缘触发和水平触发。其实这两个中文名字倒也有些贴切。这2种使用方式针对的仍然是效率问题,只不过变成了epoll_wait返回的连接如何能够更准确些。
例如,我们需要监控一个连接的写缓冲区是否空闲,满足“可写”时我们就可以从用户态将响应调用write发送给客户端 。但是,或者连接可写时,我们的“响应”内容还在磁盘上呢,此时若是磁盘读取还未完成呢?肯定不能使线程阻塞的,那么就不发送响应了。但是,下一次epoll_wait时可能又把这个连接返回给你了,你还得检查下是否要处理。可能,我们的程序有另一个模块专门处理磁盘IO,它会在磁盘IO完成时再发送响应。那么,每次epoll_wait都返回这个“可写”的、却无法立刻处理的连接,是否符合用户预期呢?
 
于是,ET和LT模式就应运而生了。LT是每次满足期待状态的连接,都得在epoll_wait中返回,所以它一视同仁,都在一条水平线上。ET则不然,它倾向更精确的返回连接。在上面的例子中,连接第一次变为可写后,若是程序未向连接上写入任何数据,那么下一次epoll_wait是不会返回这个连接的。ET叫做 边缘触发,就是指,只有连接从一个状态转到另一个状态时,才会触发epoll_wait返回它。可见,ET的编程要复杂不少,至少应用程序要小心的防止epoll_wait的返回的连接出现:可写时未写数据后却期待下一次“可写”、可读时未读尽数据却期待下一次“可读”。
 
当然,从一般应用场景上它们性能是不会有什么大的差距的,ET可能的优点是,epoll_wait的调用次数会减少一些,某些场景下连接在不必要唤醒时不会被唤醒(此唤醒指epoll_wait返回)。但如果像我上面举例所说的,有时它不单纯是一个网络问题,跟应用场景相关。当然,大部分开源框架都是基于ET写的,框架嘛,它追求的是纯技术问题,当然力求尽善尽美。
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    IO复用服务器linux C语言开发的包含客户端

    2,使用了IO复用(select监测)进行服务器编程 3,服务器总共使用一个进程,两个线程。(代码注解实际项目之中,以四个线程或者更的线程更好处理) 经验之谈: 1,服务器编程最好根据实际应用中起用多个进程与多...

    基于io复用的服务器开发例子

    总结一下,这个“基于io复用的服务器开发例子”主要展示了如何在Ubuntu环境中利用Epoll实现高效的socket编程,通过IO复用技术处理多个客户端的并发连接。这涉及到Epoll实例的创建、socket的注册、事件的监听以及异步...

    多路IO复用并发服务器模型

    ### 多路IO复用并发服务器模型 #### 一、并发服务器模型概述 并发服务器模型是一种常见的网络编程架构,用于处理客户端与服务器之间的通信。它能够有效地管理多个客户端的连接请求,确保每个客户端都能获得及时的...

    Linux网络编程之IO复用循环服务器

    ### Linux网络编程之IO复用循环服务器 #### 一、引言 在现代网络应用开发中,服务器的设计面临着越来越高的并发请求处理需求。传统的简单循环服务器每次只能处理一个客户端请求,这种方式显然无法满足高并发场景的...

    Python高级编程和异步IO并发编程

    本资源"Python高级编程和异步IO并发编程"旨在深入探讨这些主题,帮助开发者提升技能,以实现更高效、更强大的程序设计。 首先,让我们从面向对象编程(OOP)开始。在Python中,OOP是一种强大的设计模式,它允许我们...

    多路复用IO以及TCP并发服务器的实现(练习)

    IO复用是一种操作系统提供的功能,允许单个进程监控多个文件描述符(如套接字)的状态,而无需为每个文件描述符创建单独的线程或进程。这种方法提高了资源利用率,减少了上下文切换的开销。在Unix/Linux系统中,通常...

    多路IO复用并发服务器模型 ppt 信息安全 产品开发实践 课件

    多路IO复用并发服务器模型 ppt 信息安全 产品开发实践 课件

    Java IO复用_动力节点Java学院整理

    为了解决这个问题,高并发编程引入了IO复用技术,将等待消息准备好和处理消息两个阶段分开。通过使用非阻塞套接字,线程不再需要等待消息准备好,而是可以监控所有连接的状态。在Linux中,常见的IO复用机制包括...

    windows下多路复用IO(select,WSAAsyncSelect,WSAEventSelect)

    Windows下的多路复用I/O机制提供了灵活的选择来处理并发网络连接,开发者可以根据应用的需求和特性选择最适合的方法。`select`、`WSAAsyncSelect`和`WSAEventSelect`各有优缺点,理解它们的工作原理以及适用场景,有...

    iokey.rar_IO复用_Iok_Ioke_复用io_端口复用

    而"IO复用.uv2"文件可能是一个项目工程文件,如编译配置或调试信息,这与源代码的编译和运行有关。 在实际应用中,使用I/O复用可以显著提高服务器的并发能力,例如在处理网络连接请求时,服务器可以同时监听多个...

    电子政务-具备按键检测功能的IO复用端口控制电路.zip

    5. 安全与稳定性:讨论如何确保在高并发环境下,按键检测和IO复用端口控制的稳定性和安全性,包括错误处理和异常情况的预防。 6. 实例分析:可能提供一些实际案例,展示IO复用端口控制电路在电子政务系统中的具体...

    io复用修改时间

    在IT行业中,"io复用"是一个重要的概念,特别是在网络编程和系统编程领域。它涉及到如何高效地管理多个输入/输出(I/O)操作,使得一个进程或线程能够同时处理多个连接或请求,而无需为每个连接创建单独的线程。这在...

    IO复用select+多线程.rar

    本示例着重讲解了如何使用IO复用机制`select`配合多线程来构建一个高效的TCP服务器,这在Linux C环境中尤为常见。首先,我们需要理解IO复用的概念。 IO复用是一种提高服务器性能的技术,它允许单个进程监视多个文件...

    Linux网络编程之IO复用循环服务器.doc

    Linux网络编程中的IO复用循环服务器是一种高效处理并发网络请求的模型,旨在克服简单循环服务器和并发服务器的局限性。简单循环服务器每次只能处理一个请求,而并发服务器通过多线程或多进程处理多个请求,但伴随着...

    Python高性能网络编程并发框架研究.pdf

    在服务器并发编程中,除了选择合适的编程模型之外,还需要考虑一些硬件和软件因素,例如CPU、内存、磁盘I/O以及网络带宽。文中提到,优化服务器并发能力通常涉及多进程或多线程的使用,以实现用户空间和内核空间数据...

    IO复用:select,poll,epoll,kqueue的详细例子

    总的来说,理解并掌握这些IO复用机制对于开发高性能、高并发的网络应用至关重要。不同的机制有各自的优缺点和适用场景,开发者需要根据具体需求选择合适的解决方案。通过源码学习,我们可以更深入地理解这些机制的...

    多路IO复用1

    多路IO复用是一种关键的技术,广泛应用于网络编程和服务器开发中。通过合理选择和配置不同的通信模式及多路复用机制,可以显著提高服务器的性能和稳定性。理解并掌握这些技术对于开发高性能网络应用至关重要。

    IO多路复用实现TCPecho

    在IT领域,IO多路复用是一种高效地管理多个并发连接的技术,尤其在服务器端编程中,它能显著提升系统资源利用率和响应速度。本文将深入探讨如何利用IO多路复用来实现一个TCP echo服务器,该服务器运行在Linux环境下...

    IO网络编程

    在计算机科学领域,IO(Input/Output)网络编程是构建应用程序与远程系统间通信的核心技术。它涉及到数据的传输、接收、处理以及错误处理等多个环节。在这个话题中,我们将深入探讨IO网络编程的基础概念、常见模型...

Global site tag (gtag.js) - Google Analytics