我在上一篇的分享《Leader/Follower多线程网络模型介绍》中详细介绍了LF多线程网络模型的原理和代码分析。它的思路是减少线程上下文切换和数据拷贝时间,通过角色转换,来提高处理效率,尤其适用于处理短暂的、原子的并且反复的动作中的事件。可能大家都比较熟悉这种常用的思路了,那就是分层。它的核心思想:通过把异步和同步分开,用异步的方式来处理底层网络问题,用同步的方式来简化应用服务流程,通过合理的配合,达到提升效率的目的。

1、 引言

大家知道,通常高并发软件的服务处理,包括我们的旺旺,都是异步和同步同时存在的。异步用于高效地处理底层系统服务,同步则用于简化应用服务处理。要想从两种编程模型中均获益,高效地协调异步和同步服务处理是非常重要的。异步和同步服务处理通常是相关联的。例如,Web服务器的I/O层往往使用异步读操作来取得HTTP Get请求。而在CGI层对GET请求的处理则同步地运行于独立的控制线程。在I/O层异步到达的请求必须通过一种方法与CGI层对请求的同步处理集成在一起。换一个角度,我们再来看看Web客户端,AJAX可以使用异步JavaScript和XML来提高Web客户端的可感知响应速度。通常,异步和同步服务应该相互协作、取长补短。

2、 基本思想

本文介绍的HSHA方法,即HALF-SYNC/HALF-ASYNC模式,将并发软件的服务分解为异步和同步两层,然后增加一个队列层来处理二者直接的交互。使用单独的线程或者进程以同步的方式处理高层服务,比如领域功能、数据查询或文件传输,而底层系统服务则应该采用异步方式。

HALF-SYNC/HALF-ASYNC模式对这三层进行了严格的划分,这使得并发软件更容易理解、调试和改进。而且,异步和同步服务各自的缺陷也不会出现相互传染:异步服务的性能不会因为同步服务被阻塞而降低,同步服务的编程简单性也不会受到异步复杂性的影响。最后,使用排队层可以避免对异步和同步服务层之间的依赖进行硬编码,同时也可以简化消息处理的优先级排序。异步层和同步层的严格解耦合要求这两层之间的通信必须:

1)使用COPIED VALUES——这样就会带来性能上的损失和资源管理上的消耗,因为有更多的数据需要传送;

2)使用IMMUTABLE VALUES——这种做法相对来说属于轻量级的方式,但是其构造上可能更为复杂。

  总的来说,HALF-SYNC/HALF-ASYNC通过使用层的概念来保证三个不同层的执行模型和通信模型的独立性和封装性。

同步层服务,比如数据库查询、文件传输或者领域功能通常运行在自己的线程中,以便多个服务可以同时执行。

异步层的服务可以通过异步中断或者支持异步I/O的操作系统API实现,后者包括Windows overlapped I/O和I/O完成端口(I/O completion ports),或者POSIX异步I/O系统调用。如果我们将HALF-SYNC/HALF-ASYNC设计成与PROACTOR或者REACTOR事件处理基础设施联合使用,这个事件处理基础设施便是所谓的异步层。虽然REACTOR并非是真正的异步,但如果它的服务实现的是短暂(short-duration)操作,而不是长时间的阻塞,你就会发现它其实具有异步的关键属性。

  排队层通常是由同步层和异步层所有服务共享的消息队列组成。复杂的排队层可以提供多个消息队列,比如为每个消息优先级或者通信端提供一个消息队列。

3、 代码分析

和上一篇一样,我们还是来看一下开源代码实现spserver。

再次介绍一下:spserver 是一个实现了半同步/半异步(Half-Sync/Half-Async)和领导者/追随者(Leader/Follower) 模式的服务器框架,能够简化 TCP server 的开发工作。spserver 使用 c++ 实现,目前实现了以下功能:

Ø  封装了 TCP server 中接受连接的功能

Ø  使用非阻塞型I/O和事件驱动模型,由主线程负责处理所有 TCP 连接上的数据读取和发送,因此连接数不受线程数的限制

Ø  主线程读取到的数据放入队列,由一个线程池处理实际的业务

Ø  一个 http 服务器框架,即嵌入式 web 服务器

Spserver的每个版本都有一定的修改。而基于HSHA的实现则很早的版本就已经有了。在V0.7之后引入了IOCP完成端口。简单地说,iocp就是事件io操作由操作系统完成,完成后才由线程接收处理事件。代码可以在这里看到:

http://spserver.googlecode.com/svn/trunk/spserver/spiocpserver.cpp

在文件最后找到start函数:

int SP_IocpServer :: start()

{

ret = SP_IOUtils::tcpListen( mBindIP, mPort, &listenFD, 0 );

if( 0 == ret ) {

acceptArg.mListenSocket = (HANDLE)listenFD;

ret = sp_thread_create( &thread, NULL, acceptThread, &acceptArg );

SP_Executor actExecutor( 1, "act" );

SP_Executor workerExecutor( mMaxThreads, "work" );

/* Start the event loop. */

while( 0 == mIsShutdown ) {

SP_IocpEventCallback::eventLoop( &eventArg, &acceptArg );

for( ; NULL != eventArg.getInputResultQueue()->top(); ) {

SP_Task * task = (SP_Task*)eventArg.getInputResultQueue()->pop();

workerExecutor.execute( task );

}

for( ; NULL != eventArg.getOutputResultQueue()->top(); ) {

SP_Message * msg = (SP_Message*)eventArg.getOutputResultQueue()->pop();

void ** arg = ( void** )malloc( sizeof( void * ) * 2 );

actExecutor.execute( outputCompleted, arg );

}

}

delete completionHandler;

sp_close( listenFD );

}

return ret;

}

由于篇幅的关系,我只留下了关键几句代码,它的处理流程大致如下:

1)首先,侦听端口,然后通过sp_thread_create创建线程,把侦听的任务交给这个线程。

2)接下来便是循环SP_IocpEventCallback::eventLoop(),所有的 recv/send 都在 eventloop 这个函数调用上完成的。这个层就是属于异步层。

3)eventloop 在 recv 的时候,会调用 MsgDecoder.decode 函数,如果 decode 返回 OK ,说明完整地读入数据了,那么就把对应的数据放入eventArg.mInputResultQueue 里面。在 send 的时候,如果把一个 Message 完整地发送了,那么就把这个 Message 放入 eventArg.mOutputResultQueue。这两个就是队列,队列里面保存的数据一般称为完成事件。

4)workerExecutor 和 actExecutor 是两个线程池,前者用于所请求的业务处理,后者用于输出完成后的后续逻辑。由于完成事件的处理可能会涉及很复杂的业务,可能会使用到数据库或者其他,因此不能直接使用 event_loop 线程,而是使用线程池。这个就是同步层。

接下来看一下eventLoop的处理:

BOOL SP_IocpEventCallback :: eventLoop( SP_IocpEventArg * eventArg, SP_IocpAcceptArg_t * acceptArg )

{

HANDLE completionPort = eventArg->getCompletionPort();

BOOL isSuccess = GetQueuedCompletionStatus( completionPort, &bytesTransferred,

&completionKey, &overlapped, timeout );

if( ! isSuccess ) {

SP_IocpEventHelper::doClose( iocpSession->mSession );

if( lastError == WAIT_TIMEOUT ) {

onTimeout( eventArg );

}

return FALSE;

}

if( eKeyAccept == completionKey ) {

return onAccept( acceptArg );

} else if( eKeyMsgQueue == completionKey ) {

SP_IocpMsgQueue * msgQueue = (SP_IocpMsgQueue*)overlapped;

msgQueue->process();

return TRUE;

} else if( eKeyFree == completionKey ) {

delete iocpSession->mSession;

free( iocpSession );

return TRUE;

} else {

if( SP_IocpEvent_t::eEventRecv == iocpEvent->mType ) {

onRecv( iocpSession );

return TRUE;

}

if( SP_IocpEvent_t::eEventSend == iocpEvent->mType ) {

onSend( iocpSession );

return TRUE;

}

}

return TRUE;

}

上面也同样省掉了大量代码。大家可能到这里看源码:

http://spserver.googlecode.com/svn/trunk/spserver/spwin32iocp.cpp

1)、首先是检查完成端口的完成情况。如果失败了,会进行错误处理,比如超时等。如果标记是消息队列中的消息,则还会调用消息的process。

2)、然后判断完成事件的类型,分别进行onAccept,free,onRecv,onSend的处理。

4、模式分析

HSHA模式也可以从 生产者/消费者 的角度来描述。在 spserver 中,同步层和异步层轮流扮演这两个角色。异步层从 socket 读入数据,然后放到同步层的队列中,这个时候异步层是生产者,同步层是消费者。同步层处理完之后,把输出数据放到异步层的队列中,这个时候同步层是生产者,异步层是消费者。事实上,这种方式也在大多数操作系统采用的方式,下图是Unix的消息处理示意图。

异步层的关键是要响应快速,所以它的关键字应该是被动。实现一个结构清晰的异步I/O框架,有多种方式。

  • 使用Reactor或Proactor反应堆模式实现多路事件的处理,反应堆模式使用一个单线程的处理循环,把多路的事件派发给多个处理者。这个模式组合了单线程处理循环的简单性和面向对象编程提供的可扩展性。反应堆模式在一个线程(或进程)中进行顺序的消息处理,常用来消除多线程同步和加锁处理的复杂性。一个反应堆可以使用在同步或者异步的事件源上。但是它支持的事件处理者的行为要求是异步的。也就是说,为了不影响其它事件源的响应效率,事件处理者是不能阻塞的。
  • 实现一个多级的中断处理机构。这种机构下,当更高级别的任务(如硬中断)要求处理的时候,当前的进程可以被中断。为了防止共享的状态访问时被破坏,异步层使用的数据结构必须被保护(例如提升处理器级别或使用信号量)。例如,在一个操作系统内核中,硬件中断的服务时间很大程度上决定了对多级中断处理机构的需要。如果这个时间能够显著的减少,可以把所有的处理放到硬中断的层次上,这样就可以避免软中断的过度资源消耗。TCP/IP的实现中,就减少输入包的协议处理时间化费,让所有的包处理过程可以在两级中断机构实现。

同步层的关键字是主动,主动方式可以让程序更加简单可靠。系统中经常会有时间长的任务,如执行大量流数据传输,或者是等待服务器响应的数据查询。使用主动对象模型(活动对象)实现这些长时任务。活动对象拥有自己的运行栈和寄存器状态,在执行同步I/O的时候可以被阻塞。实现活动对象结构,要求一个控制线程的切换机制,需要一个地方存放和恢复线程的状态(如寄存器的值,栈指针),这些功能足够实现一个非抢占的,没有内存保护的线程机构。任何操作系统的用户线程都可以做到这一点。

队列层的关键字则是安全高效。一般需要考虑以下几个问题:

  • 并行控制。如果同步任务和异步任务的执行是并行的(如论使用多CPU还是硬件中断),为了避免争用,共享的队列的状态变化必须是连续的。因此,实现队列层的时候,经常使用并行控制机制,如信号量,互斥体和条件变量等。当消息在队列中插入或删除的时候,并行控制保证队列的内部数据结构不被破坏。
  • 层到层之间的流量控制。在队列层缓存消息,系统不能提供无限的资源。因此,必须控制同步和异步层之间传输的数据量。例如,在层到层的流控制下,避免同步层的数据量超过网络接口能够传输的极限。同步任务可以被阻塞。因此,可以使用下面的策略:如果其排队超过一定的量,可以让任务阻塞。当异步任务层把排队数降低到一定的水平,就可以重新唤醒同步任务继续执行。相对地,异步层地任务不能被阻塞,当处理过量地数据时,队列层要根据策略丢弃消息。这种情况下,如果使用了一个可靠的,面向连接的网络协议,发送者最终会发现传输超时,要求重新传输数据。
  • 数据拷贝消耗。有些系统把队列层放到了用户和内核之间的保护边界上。为了分离不同的保护域,一般使用拷贝数据的方法。然而,这增加了系统总线和内存的负担。当大数据量的消息传输的时候,这可能会降低很大的性能。一种减少数据拷贝的方式:分配一个专用的内存区,这个内存区被同步任务层和异步任务层共享。这样,两层之间可以高效率的交换数据,不需要拷贝。

顺便说一下,这类东西大师级作品还是得靠老外啊。这里有大师级的HSHA介绍,本文从中翻译了一点,其它自己看吧:

http://www.cs.wustl.edu/~schmidt/PDF/HS-HA.pdf

如有问题,请指教。谢谢大家。

HSHA多线程网络编程模型介绍相关推荐

  1. 【Linux服务器开发系列】详解多线程网络编程丨百分百干货分享丨学到就是赚到

    90分钟搞懂多线程网络编程模型 1. 网络编程关注的问题 2. 网络编程的几种模型reactor,one loop per thread及其变种 3. skynet,redis,nginx,memca ...

  2. aio 系统原理 Java_Java新一代网络编程模型AIO原理及Linux系统AIO介绍

    从JDK 7版本开始,Java新加入的文件和网络io特性称为nio2(new io 2, 因为jdk1.4中已经有过一个nio了),包含了众多性能和功能上的改进,其中最重要的部分,就是对异步io的支持 ...

  3. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--模型比较>一文中,我们分析了各种模型在处理短连接时的能力.本文我们将讨论处理长连接时各个模型的性能.(转载请指明出于b ...

  4. 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较

    经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤.现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客) 模型编程步骤对比 <朴素.Select.Poll和Ep ...

  5. 朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型

    做Linux网络开发,一般绕不开标题中几种网络编程模型.网上已有很多写的不错的分析文章,它们的基本论点是差不多的.但是我觉得他们讲的还不够详细,在一些关键论点上缺乏数据支持.所以我决定好好研究这几个模 ...

  6. day16多线程网络编程日志枚举

    多线程&网络编程 一.实现多线程 1.1 相关概念 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的一条执行路径.实际运作单位.简单理解:应用软件中互相独立,可以同时运 ...

  7. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型

    在阅读完<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>和<朴素.Select.Poll和Epoll网络编程模型实现和分析--Poll模型> ...

  8. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>中,我们分析了它只能支持1024个连接同时处理的原因.但是在有些需要同时处理更多连接的情况下,102 ...

  9. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--朴素模型>中我们分析了朴素模型的一个缺陷--一次只能处理一个连接.本文介绍的Select模型则可以解决这个问题.(转载 ...

最新文章

  1. bzoj 1086: [SCOI2005]王室联邦
  2. 使用java的Calendar对象获得当前日期的上几个度开始、结束时间
  3. pandas编写自定义函数计算多个数据列的加和(sum)、使用groupby函数和apply函数聚合计算分组内多个数据列的加和
  4. Java B2B2C多用户商城 springcloud架构-服务容错保护(Hystrix服务降级)
  5. win7如何添加开机启动程序(开机就自动运行打开)
  6. python内置collections模块的使用
  7. Delphi调用REST
  8. mysql数据每日更新_[每日更新-MySQL]4.记录操作(数据操作)
  9. python2和python3的不同点_Python2和Python3的区别,新手学习Python应该如何选择
  10. ubuntu mv和cp命令
  11. leetcode323. 无向图中连通分量的数目
  12. 记录一个相当好用的反编译工具下载地址
  13. 学.net还是php,ASP.NET和php哪个更容易学
  14. QString::arg()//用字符串变量参数依次替代字符串中最小数值
  15. python json loads 中文乱码_python实现智能语音天气预报
  16. 【网络信息安全】网络信息安全概述
  17. repo init 是啥意思
  18. java 开源客服系统_一个开源的智能客服系统
  19. Java qq登录界面设计
  20. matlab设计光栅,光栅原理及MATLAB仿真.doc

热门文章

  1. AutoComplete选择之后,传PK触发动作
  2. 以下哪一个不属于python语言的特点-以下不属于python语言特点的是( )_学小易找答案...
  3. 零基础学python爬虫-零基础如何学爬虫技术?一篇带你入门!(理论+实操+荐书)...
  4. python开发工具排名-7款公认比较出色的Python IDE,你值得拥有!
  5. python文字教程-Python 爬虫零基础教程(3):输出一个网页上的文字
  6. r语言和python-r语言和python学哪个
  7. python的软件叫什么-Python 是什么软件?
  8. python软件界面-用Html来写Python桌面软件的UI界面-htmlPy
  9. 大学python和vb哪个简单-vb和python哪个速度快
  10. python3在线-荐python3在线编程输入输出总结