多进程程序的框架模板

so sorry 的,这个文档的主要内容都是绘图的,贴上来图都没了但是图里面的文字却还在,看起来莫名其妙的。

多进程服务器框架

张博(Zb++ 2012

多进程服务器框架... 1

张博(Zb++ 2012. 1

1       功能说明... 2

2       Job和任务,分发和汇总... 3

3       流程和接口... 4

4       核心设计(系统实现的功能)... 4

4.1             JOB数据... 4

4.2             任务数据状态... 5

4.3             子进程状态... 5

4.4             子进程控制命令... 6

4.5             子进程休眠/唤醒机制... 6

4.6             信号丢失... 7

4.7             子进程循环... 7

4.8             分发(主控)进程循环... 8

4.9             分发(主控)进程处理一个job. 9

4.10          高低水(自动进程数控制)... 10

5       使用说明... 10

5.1             编写用户数据结构... 10

5.1.1         共享内存数据结构限制... 10

5.1.2         共享内存免用锁指导... 11

5.1.3         Job信息(模板参数T_JOBINFO... 12

5.1.4         任务信息(模板参数T_TASKINFO... 12

5.1.5         用户服务数据结构(模板参数T_SERVERINFO... 12

5.2             实现三个接口... 12

5.2.1         接口规范... 12

5.2.2         IDistributeTask... 12

5.2.3         IProcessTask接口... 13

5.2.4         IRollup接口... 13

5.3             使用CMultiProcessServer. 13

5.3.1         定义... 13

5.3.2         构造函数... 14

5.3.3         获得用户服务数据区... 14

5.3.4         运行... 14

5.3.5         控制... 14

5.3.5.1     控制说明... 14

5.3.5.2     查看数据... 15

5.3.5.3     暂停... 15

5.3.5.4     继续... 15

5.3.5.5     停止... 15

5.3.5.6     运行模式... 15

5.3.5.7     子进程coredump是否退出... 15

5.3.5.8     自动睡眠控制... 15

5.3.5.9     修改允许的最大处理进程数... 16

6       性能调节... 16

6.1             与性能有关的因素... 16

6.2             最佳情形... 17

6.3             性能调节的原则... 18

6.4             性能调节参数和因素... 18

7       示例... 18

7.1             三个用户数据结构... 18

7.2             三个接口... 18

7.3             运行和控制代码... 20

 

1      功能说明

本系统将多进程并发处理程序的框架模板化,客户代码仅需要定义业务处理代码即可实现可靠的并发处理。

任务分发进程

 

任务汇总进程

 

 

处理

 

 

 

处理

 

 

 

处理

 

 

 

处理

 

 

 

处理

 

 

 

处理

 

 

 

处理

共享内存,包含N个任务队列,每个队列由一个处理进程处理,分发进程和汇总进程处理所有队列

一般业务的并发处理通常为如下模式:

逐行读取文件

处理

输出结果

处理

处理

接收请求

处理

处理

处理

文件处理

网络服务

2      Job和任务,分发和汇总

本系统中使用Job来代表一项工作,例如对一个文件的处理,使用任务来代表可以并发的一个处理,例如对每一条记录的操作。

分发代表并发处理之前的操作,汇总代表并发处理之后的操作,例如分开可以是打开文件读取每一行,而汇总就是输出文件。

3      流程和接口

初始化共享内存,创建汇总进程

汇总进程被创建

Run

分发进程

IDistributeTask

汇总进程

IRollupTask

OpenJob

DistributeTask

isJobEnd

CloseJob_Distribute

完成/需汇总

处理进程,每个队列对应一个,不同队列的进程之间没有交叉

IProcessTask

新建/需处理

空闲/已汇总

T_TASKINFO

第一个

OpenJob_Rollup

查找位置创建处理进程

Process(多个)

由任务队列对应的进程循环处理

 

Rollup

最后一个完成

CloseJob_Rollup

4      核心设计(系统实现的功能)

4.1    JOB数据

在用/空闲

是否分发完成

分发数

汇总数

用户数据,传递给接口的每个函数

4.2    任务数据状态

空闲

新建

完成

分发进程

处理进程

汇总进程

4.3    子进程状态

未用

创建

运行

暂停

异常

退出

分发进程创建处理和汇总进程

分发进程或监控程序发现异常并设置

子进程标记自己为运行

Sleep睡眠,SIGCONT唤醒,超时苏醒则再次睡眠

子进程标记自己为退出

4.4    子进程控制命令

运行

暂停

退出

暂停命令

 

无命令/清除命令

 

退出命令

 

命令设计为状态性质,即命令可以一直保持,子进程根据命令适当处理。当命令处于暂停状态时子进程仍然会因为sleep时间到而唤醒,但会根据暂停命令再次进入sleep,目前设计唤醒后仍然会检查是否有任务需要处理,逻辑上暂停中不会分发任务,所以不会有任务需要处理。

4.5    子进程休眠/唤醒机制

发送信号时间

子进程sleep

子进程sleep结束,清除信号发送时间

主进程发送CONT信号,设置发送信号时间

信号发送时间不是同一秒

中断sleep

通过该标志避免快速发送大量信号给进程

4.6    信号丢失

不用锁的互斥机制是不可靠的,可能存在这种情形:检测到子进程处于暂停中,子进程苏醒,主进程发送信号,信号丢失,子进程再次进入睡眠,主进程认为信号已经发出而不再发送信号。

本系统采用比较小的睡眠时间来减少信号丢失造成的延迟。

4.7    子进程循环

命令处理首先确认当前命令序列号与循环前一致,这样可以保证发出命令前的所有任务都已经被处理

获得命令

处理/汇总

命令处理->

命令序列号与循环前一致?

退出命令:exit(0)

暂停命令:sleep(N)

空命令:sleep(1)

4.8    分发(主控)进程循环

是否已经处于暂停中

检查子进程是否异常

无命令/正常运行

处理一个job

停止所有子进程

退出

暂停命令

初次处理设置子进程命令

Sleep(1)

退出命令

停止所有子进程

退出

出错

任何其它命令清除此标志

主控进程正常处理时job完成后不设休眠,如果需要降低无job时循环检查的CPU占用率,应该在接口函数OpenJob中适当处理(例如使用sleep或带超时的select)。

4.9    分发(主控)进程处理一个job

OpenJob

清除子进程命令

唤醒子进程

isJobEnd

查找一个空闲task位置

查找、等待、处理进程异常、高低水控制

DistributeTask

Task状态置为NEW

创建或唤醒子进程和汇总进程

CloseJob

4.10       高低水(自动进程数控制)

进程0  有任务,运行

进程1  有任务,运行

进程2  。。。

进程3

进程4

进程5

进程6

进程7  无任务,睡眠

进程8  无任务,睡眠

进程9  无任务,睡眠

得不到任务的进程自动进入睡眠-苏醒-睡眠循环,只占用很少的CPU

下箭头: 顺序分发

5      使用说明

5.1    编写用户数据结构

5.1.1  共享内存数据结构限制

存放于共享内存的数据不能包含任何指向私有内存的指针,任何指针类型都不能使用,以及任何包含动态数据分配的类,比如STLstring。除非专门为共享内存设计,否则只能用基本数据类型(int long bool char [])。Zbstd::sstring(2s开头)是专门为共享内存设计的string替代类。

本系统使用的任何结构必须包含如下函数,用于把数据输出为字符串:

string& toString(string & ret)const

返回值应为参数str的引用(这样设计的目的是为了避免动态对象)。

例如:

string& toString(string & ret)const

{

         Ret=””;

         Ret+=…

         Ret+=…

         Return ret;

}

为了避免地址对齐问题,建议保证每个结构至少包含一个long。为了保险起见,可以在结构最开始定义一个无意义的long

例如:

Struct A

{

         Long ____;

         Int a;

        

}

5.1.2  共享内存免用锁指导

鉴于共享内存是多进程共享的,使用锁有时候是不可避免的。但是锁会带来很大的系统资源消耗,并且滥用锁会失去多进程的意义,因此应尽可能避免需要使用互斥锁的情形。为了调式和监控的需要,应保证任何时候显示共享内存数据都是安全的。所以应该避免设计动态化的“灵活”的数据结构,尽量使用简单数组,通过索引位置访问,不排序(否则很难避免读写锁的使用)。

在不使用锁的情况下对共享内存做连续判断可能会发生冲突,例如下面的情形:

If(1==state ||2==state)

该代码是不可靠的,原因是如果同时另外一进程正在把state2改为1则可能判断结果出错:

进程1 读到state2,不符合1==state

                    进程2修改state1

进程1读到state1,不符合2==state

于是返回false,但实际上应该返回true

避免此问题应提前保存要判断的数据:

Long tmp=state;

If(1==tmp || 2==tmp)

很显然,只能对一个简单数据类型这样做,对多个数据无论如何不能避免上面的情形。在某些巧妙设计下,可以通过检查判断前后数据是否发生改变来确定刚才的判断是否有效。

5.1.3  Job信息(模板参数T_JOBINFO

每个job的数据结构,作为分发、处理和汇总的参数。

该结构存放于共享内存,需要遵循共享内存数据结构限制。

5.1.4  任务信息(模板参数T_TASKINFO

每个任务的数据结构,放在任务队列中,作为分发、处理和汇总的参数。

该结构存放于共享内存,需要遵循共享内存数据结构限制。

5.1.5  用户服务数据结构(模板参数T_SERVERINFO

用户定义的数据,完全由用户使用,可以用于存放各进程公共数据。该结构通过CMultiProcessServergetServerInfo获得。

该结构存放于共享内存,需要遵循共享内存数据结构限制。

5.2    实现三个接口

5.2.1  接口规范

接口返回值说明:一般均定义为 bool fun(...,ret)的形式,函数返回值表达是否执行出错(系统错误,不再继续运行),ret表达处理结果。

5.2.2  IDistributeTask接口

该接口用于分发进程。

         template<typename T_JOBINFO,typenameT_TASKINFO>

         classIDistributeTask

         {

         public:

                   //打开job,若成功打开ret=true,否则没有job可做

                   virtualbool OpenJob(T_JOBINFO * pJobInfo,bool * ret)=0;

                   //分发一个task,放入pTaskInfo,如果发生数据性错误ret=false,仍继续处理

                   virtualbool DistributeTask(T_JOBINFO *pJobInfo,T_TASKINFO * pTaskInfo,bool * ret)=0;

                   //是否job结束

                   virtualbool isJobEnd(T_JOBINFO * pJobInfo,bool * ret)=0;

                   //结束job

                   virtualbool CloseJob_Distribute(T_JOBINFO *pJobInfo)=0;

         };

 

5.2.3  IProcessTask接口

该接口用于任务处理进程。

         template<typename T_JOBINFO,typenameT_TASKINFO>

         classIProcessTask

         {

         public:

                   virtualbool Process(T_JOBINFO * pJobInfo,T_TASKINFO *pTaskInfo,bool * ret)=0;

         };

 

5.2.4  IRollup接口

该接口用于汇总进程。

         template<typename T_JOBINFO,typenameT_TASKINFO>

         classIRollupTask

         {

         public:

                   //打开job

                   virtualbool OpenJob_Rollup(T_JOBINFO * pJobInfo)=0;

                   virtualbool Rollup(T_JOBINFO * pJobInfo,T_TASKINFO *pTaskInfo,bool * ret)=0;

                   //结束job

                   virtualbool CloseJob_Rollup(T_JOBINFO * pJobInfo)=0;

         };

 

5.3    使用CMultiProcessServer

5.3.1  定义

         template<typename T_JOBINFO,typenameT_SERVERINFO,typename T_TASKINFO,long MAX_JOB,long MAX_PROCESS,long MAX_PROCESS_TASK>

         classCMultiProcessServer

 

 

第一第二第三个参数是JOB信息数据类型、用户服务数据类型和任务信息数据类型

MAX_JOB是最大同时打开的job数,因为分发和处理是不同步的。

MAX_PROCESS定义最大处理进程数,也就是任务队列数。

MAX_PROCESS_TASK定义每个队列的任务个数。

5.3.2  构造函数

                   CMultiProcessServer(IDistributeTask<T_TASKINFO> * d,IProcessTask<T_TASKINFO > * p,IRollupTask<T_TASKINFO > *r,char * shmbuf)}

 

前三个参数为三个接口,第四个参数为共享内存存储区指针,该指针有用户提供,必须指向共享内存,并且大小不得小于“static long CMultiProcessServer::calcBufSize()”,该静态成员根据模板参数计算所需的共享内存存储区大小(该存储区包含框架使用的数据、任务队列数据、用户服务数据)。这样设计的目的是用户项目可能提供了专门的共享内存管理。

5.3.3  获得用户服务数据区

调用getServerInfo()既可以获得用户服务数据区指针。应该在运行服务之前调用,一旦进入运行,控制便不会再回到用户代码,除非服务结束。

5.3.4  运行

void clear()

int run()

 

调用clear()初始化系统,清空共享内存,预设参数。

设置初始参数,设置方式见《控制》章节。

调用run()启动服务。根据运行参数设置在没有Job可做或发生系统错误或收到退出指令时返回。

5.3.5  控制

5.3.5.1 控制说明

控制必须由独立的进程进行,控制进程具有和执行进程(指运行服务的进程,即调用run的进程)相同的初始化方式,但不调用clearrun

暂停或停止只在Job结束后处理并且等待所有任务汇总完成。如果需要随时暂停,可以设计为每个Job一个任务。

5.3.5.2 查看数据

string &toString(string & ret)const

具体数据的解读参考性能调节章节。

5.3.5.3 暂停

void SetCommondPause()

5.3.5.4 继续

void SetCommondContiue()

5.3.5.5 停止

void SetCommondExit()

5.3.5.6 运行模式

void SetExitIfNoJob(boolexitIfNoJob)

是否在没有job时退出。

5.3.5.7 子进程coredump是否退出

void SetExitIfProcessCoredump(bool exitIfProcessCoredump)

设置为true则在处理进程coredump时全部退出,否则自动重起。

汇总进程coredump总是全部退出。

分发进程courdump总是全部退出。

5.3.5.8自动睡眠控制

void SetNotAutoSleep(bool)

处理进程和汇总进程无任务可做以及分发失败时进程是否通过睡眠来放弃CPU时间,具体效果参考性能调节章节。

5.3.5.9 修改允许的最大处理进程数

void SetMaxProcess(longmax_process)

1-MAX_PROCESS,超出此范围不修改或设置为MAX_PROCESS(如果当前未设置)。

6      性能调节

6.1    与性能有关的因素

多进程服务的性能是一个多种因素相互作用的结果。

进程数增加,吞吐量上升

磁盘寻道时间增加

CPU进程调度和内存交换上升

进程数增加导致CPU进程调度和和内存交换上升

CPU调度繁忙反过来抑制吞吐量

磁盘繁忙抑制吞吐量

不停循环增加CPU占用率

 

CPU无效繁忙导致实际利用率下降

 

睡眠减少CPU占用率

 

睡眠放弃时间片导致吞吐量降低

6.2    最佳情形

最大吞吐量情形下分发进程、处理进程、汇总进程全部满负荷运转,无等待时间(没有无效循环或自动睡眠)。从技术指标上看就是分发进程分发失败为0、处理进程和汇总进程空循环为0(空循环为0自然睡眠也为0),处理进程至少有一个CPU占用率100%,分发进程或汇总进程CPU占用率100%

仅仅分发失败和空循环为0CPU占用率低并不能达到最大吞吐量,CPU100%但存在分发失败或空循环也并非最大吞吐量。

6.3    性能调节的原则

鉴于性能是多个因素互相抑制的结果,单纯考虑单一因素不能达到最佳状态,平衡状态也并非一定是最佳状态,而且存在多个平衡状态的可能,因此需要尝试各种参数组合来找到最佳状态。

6.4    性能调节参数和因素

MAX_JOB

同时打开的job

考虑业务需要,足够即可

MAX_PROCESS

最大处理进程数

足够大,保持最后一个处理进程非满负荷,即既有明显偏少的处理任务数,如果禁用自动睡眠多余的进程会占用过多CPU

MAX_PROCESS_TASK

每进程最大任务数

至少为3,因为任务有三种状态,加大此参数可以减少分发失败

分发失败时的状态

查看toString的数据

任务NEWDONE多说明处理进程处理能力不足,可以增加MAX_PROCESS参数。若DONE多说明汇总进程处理能不足,考虑将更多的业务放在处理进程而不是汇总进程

自动睡眠

分发失败或空循环放弃CPU

禁用自动睡眠可以提高吞吐量,但是最佳状态是平衡态,无需自动睡眠。不追求极限吞吐量时应使用自动睡眠避免过度占用CPU

 

 

7      示例

7.1    三个用户数据结构

均使用样板数据结构CDemoData,该结构含有一个成员long n。服务数据结构在示例代码中并未使用。

7.2    三个接口

示例代码直接使用了三个接口的测试功能,每个job含有8tasktask生成时放入一个序列整数(*100),处理时+1,汇总时+10,汇总后的数据后两位应该是“11”。

接口处理的参数pJobInfopTaskInfo均为CDemoData *

         //分发接口

         template<typename T_JOBINFO,typenameT_TASKINFO>

         classIDistributeTask

         {

         public:

                   //打开job,若成功打开ret=true,否则没有job可做

                   virtualbool OpenJob(T_JOBINFO * pJobInfo,bool * ret)

                   {

                            static long n=0;

                            pJobInfo->n=(++n)*1000;

                            *ret=true;

                            return true;

                   }

                   //分发一个task,放入pTaskInfo,如果发生数据性错误ret=false,仍继续处理

                   virtualbool DistributeTask(T_JOBINFO *pJobInfo,T_TASKINFO * pTaskInfo,bool * ret)

                   {

                            static long n=0;

                            pTaskInfo->n=(++n)*100;

                            *ret=true;

                            sleep(1);

                            return true;

                   }

                   //是否job结束

                   virtualbool isJobEnd(T_JOBINFO * pJobInfo,bool * ret)

                   {

                            static long n=0;

                            if(n>8)

                            {

                                     *ret=true;

                                     n=0;

                            }

                            else

                            {

                                     *ret=false;

                                     ++n;

                            }

                            return true;

                   }

                   //结束job

                   virtualbool CloseJob_Distribute(T_JOBINFO * pJobInfo)

                   {

                            pJobInfo->n+=1;

                            return true;

                   }

         };

         //处理接口

         template<typename T_JOBINFO,typenameT_TASKINFO>

         classIProcessTask

         {

         public:

                   virtualbool Process(T_JOBINFO * pJobInfo,T_TASKINFO *pTaskInfo,bool * ret)

                   {

                            for(longi=0;i<10000;++i)

                            {

                            }

                            pTaskInfo->n+=1;

                            *ret=true;

                            return true;

                   }

         };

         //汇总接口

         template<typename T_JOBINFO,typenameT_TASKINFO>

         classIRollupTask

         {

         public:

                   //打开job

                   virtualbool OpenJob_Rollup(T_JOBINFO * pJobInfo)

                   {

                            pJobInfo->n+=10;

                            return true;

                   }

                   virtualbool Rollup(T_JOBINFO * pJobInfo,T_TASKINFO *pTaskInfo,bool * ret)

                   {

                            pTaskInfo->n+=10;

                            *ret=true;

                            return true;

                   }

                   //结束job

                   virtualbool CloseJob_Rollup(T_JOBINFO * pJobInfo)

                   {

                            pJobInfo->n+=100;

                            return true;

                   }

         };

 

7.3    运行和控制代码

本代码直接创建了共享内存,使用了固定的key,在实际使用中应该避免使用固定的key。运行服务的函数每次都尝试创建共享内存,在正式代码中这样做是不合适的。应该将共享内存管理作为独立的功能来使用。

两个函数一个运行一个控制。

注意关于定义和构造部分必须保持一致,正式代码应该将定义和构造编写入一个类中以免无意中造成差异。

typedefzbstd::CMultiProcessServer<CDemoData,CDemoData,CDemoData,10L,5L,3L >MPS_T;

 

key_t key=0x10000;

zbstd::IDistributeTask<CDemoData,CDemoData> distribute;

zbstd::IProcessTask<CDemoData,CDemoData> process;

zbstd::IRollupTask<CDemoData,CDemoData> rollup;

 

int test_CMultiProcessServer(int argc,char ** argv)

{

         thelog<<"多进程服务器测试"<<endi;

         intshmid=zbstd::CShmMan::CreateSHMByKey(key,MPS_T::calcBufSise());

         //if(shmid<=0)return__LINE__;

         char *pshm=zbstd::CShmMan::ConnectByKey(key,false);

         if(NULL==pshm)

         {

                   zbstd::CShmMan::Destory(shmid);

                   return__LINE__;

         }

 

         string str;

         MPS_T mps(&demoMPS,&demoMPS,&demoMPS,pshm);

         mps.clear();

         mps.SetMaxProcess(5);

         mps.SetExitIfNoJob(true);

         mps.SetNotAutoSleep(false);

         mps.SetExitIfProcessCoredump(false);

         thelog<<endl<<mps.toString(str)<<endi;

         mps.run();

 

         thelog<<endl<<mps.toString(str)<<endi;

 

         zbstd::CShmMan::Disconnect(pshm);

         //zbstd::CShmMan::Destory(shmid);

         return0;

}

int test_CMultiProcessServer_view(int argc,char **argv)

{

         thelog<<"多进程服务器查看"<<endi;

         char *pshm=zbstd::CShmMan::ConnectByKey(key,false);

 

         string str;

         MPS_T mps(&distribute,&process,&rollup,pshm);

         while(true)

         {

                   string cmd=UIInput("p=pause s=stop c=continue a=auto_sleepna=not_auto_sleep sp=set_max_process other=show","");

                   thelog<<endl<<mps.toString(str)<<endi;

                   if("p"==cmd)mps.SetCommondPause();

                   elseif("s"==cmd)mps.SetCommondExit();

                   elseif("c"==cmd)mps.SetCommondContiue();

                   elseif("a"==cmd)mps.SetNotAutoSleep(false);

                   elseif("na"==cmd)mps.SetNotAutoSleep(true);

                   elseif("sp"==cmd)mps.SetMaxProcess(atol(UIInput("intput 1-MAX:","").c_str()));

                   else;

         }

         zbstd::CShmMan::Disconnect(pshm);

         return0;

}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值