2007-12-09
SPProcPool: Unix/Linux 上的进程池服务器框架
关键字: process pool 进程池
SPProcPool 是一个 linux/unix 平台上的进程池服务器框架,使用 c++ 实现。主要包含了几种不同类型的进程池的实现:
一个基于 Leader/Follower 模式的服务器端进程池(类似 apache 的 prefork 模型);
一个组合了 Prefork 和线程池的服务器端框架(类似 apache 的 worker 模型);
一个基于文件句柄传递的服务器端进程池;
一个用于非服务器端的,能够在多线程或事件驱动环境下使用的进程池。
主页:http://code.google.com/p/spprocpool/
下载:spprocpool
关于进程池服务器框架,在《unix网络编程(第二版)》的第 27 章,详细地描述了多种不同的实现方式。
基于 Leader/Follower 模式的实现:27.6 TCP 预先派生子进程服务器程序,accept 无上锁保护
基于文件句柄传递:27.9 TCP 预先派生子进程服务器程序,传递描述字
关于非服务器端的,能够在多线程或事件驱动环境下使用的进程池,这里做一个比较详细的说明。
多线程的好处是各个线程能够共享一个地址空间,因此对一些需要全局排序来调度的任务,使用多线程可以比较方便地实现。比如类似 postfix/qmgr 的模块,如果使用多线程的话,那么所有的邮件能够在一个优先队列中排队,按队列顺序进行投递;如果投递失败了,那么重新插入队列。
但另一方面,如果具体的任务处理部分已经有了实现,但不是线程安全的,这种问题要怎么来解决呢?
一个最直观的解决方法是每个任务直接 fork 一次。但是这种做法有几个细节问题需要认真考虑:
1.在子进程中如何把结果返回给调度进程?常见的方法是使用 pipe
2.每个任务 fork 一次,估计很多人的第一反应就是性能会怎么样?
3.如果调度进程中,除了负责 fork 的线程,还有其他的线程存在,那么就存在 fork-with-thread 的问题。
>>具体的内容可以参考:http://www.opengroup.org/onlinepubs/000095399/functions/fork.html
针对上面存在的问题,在每个任务直接 fork 一次的基础上,做了一些改进,就形成了这样的一个进程池实现:
1.在前面的方案中,存在调度进程(Sched)和工作进程(Worker)这两类进程。
>>为了避免 fork-with-thread 的问题,再增加一类管理进程(Manager)。管理进程的主要职责就是负责 fork 工作进程。
2.通常 Manager 是由 Sched fork 出来的,它们之间存在一条通信的 pipe (MgrPipe) 。
>>创建一个新的工作进程的流程如下:Sched 创建一个 pipe (WorkerPipe),把其中的一端用 send_fd 的方法发送给 Manager,
>>然后 Manager fork 一个 Worker 出来,并且把 WorkerPipe 传递给 Worker 。这样就在 Sched 和 Worker 之间建立了一个 Pipe 。
3.Worker 在被 fork 出来之后,通常就阻塞在读 WorkerPipe 上面。Sched 通过 WorkerPipe 发送任务给 Worker 。
>>Worker 完成任务之后,通过 WorkerPipe 发送结果给 Sched 。Worker 可以不断地重复这个过程,这样就达到了一个池的效果。
4.对于使用 libevent 这类事件驱动类型的程序,这个进程池也能方便地被调用。
>>因为 Worker 曝露出来的是一个 PipeFd,能够方便地加入到 libevent 的事件循环中。这类事件驱动类的程序,
>>通常使用单线程实现,当具体的任务处理可能需要耗费比较长时间的时候,就需要使用多线程或者多进程来辅助了。
SPProcPool 提供了 3 个服务器框架:
SP_ProcInetServer(传递文件句柄)
SP_ProcLFServer(Leader/Follower模型)
SP_ProcMTServer(Worker模型)
它们的定义很类似:
从接口中可以看到,要使用这些框架,需要提供一个 SP_ProcInetServiceFactory 类的实例。这个类相关的定义如下:
这里使用的是典型的抽象工厂方法。在工厂类中,除了 create 方法之外,还有两个特别的方法:workerInit 和 workerEnd 。workerInit 在子进程开始运行的时候被调用,workerEnd 在子进程退出的时候被调用。在 Service 类中,只有一个 handle 方法,它的参数就是已经 accept 到 socket 。
下面以一个简单的服务器为例进行说明。这个服务器是模仿《unix网络编程(第二版)》(中文版)第27章的服务器。服务器从 socket 读入包含一个数字的一行,然后根据这个数字返回相应的内容。
要实现这个简单的服务器例子,代码如下:
一个基于 Leader/Follower 模式的服务器端进程池(类似 apache 的 prefork 模型);
一个组合了 Prefork 和线程池的服务器端框架(类似 apache 的 worker 模型);
一个基于文件句柄传递的服务器端进程池;
一个用于非服务器端的,能够在多线程或事件驱动环境下使用的进程池。
主页:http://code.google.com/p/spprocpool/
下载:spprocpool
关于进程池服务器框架,在《unix网络编程(第二版)》的第 27 章,详细地描述了多种不同的实现方式。
基于 Leader/Follower 模式的实现:27.6 TCP 预先派生子进程服务器程序,accept 无上锁保护
基于文件句柄传递:27.9 TCP 预先派生子进程服务器程序,传递描述字
关于非服务器端的,能够在多线程或事件驱动环境下使用的进程池,这里做一个比较详细的说明。
多线程的好处是各个线程能够共享一个地址空间,因此对一些需要全局排序来调度的任务,使用多线程可以比较方便地实现。比如类似 postfix/qmgr 的模块,如果使用多线程的话,那么所有的邮件能够在一个优先队列中排队,按队列顺序进行投递;如果投递失败了,那么重新插入队列。
但另一方面,如果具体的任务处理部分已经有了实现,但不是线程安全的,这种问题要怎么来解决呢?
一个最直观的解决方法是每个任务直接 fork 一次。但是这种做法有几个细节问题需要认真考虑:
1.在子进程中如何把结果返回给调度进程?常见的方法是使用 pipe
2.每个任务 fork 一次,估计很多人的第一反应就是性能会怎么样?
3.如果调度进程中,除了负责 fork 的线程,还有其他的线程存在,那么就存在 fork-with-thread 的问题。
>>具体的内容可以参考:http://www.opengroup.org/onlinepubs/000095399/functions/fork.html
针对上面存在的问题,在每个任务直接 fork 一次的基础上,做了一些改进,就形成了这样的一个进程池实现:
1.在前面的方案中,存在调度进程(Sched)和工作进程(Worker)这两类进程。
>>为了避免 fork-with-thread 的问题,再增加一类管理进程(Manager)。管理进程的主要职责就是负责 fork 工作进程。
2.通常 Manager 是由 Sched fork 出来的,它们之间存在一条通信的 pipe (MgrPipe) 。
>>创建一个新的工作进程的流程如下:Sched 创建一个 pipe (WorkerPipe),把其中的一端用 send_fd 的方法发送给 Manager,
>>然后 Manager fork 一个 Worker 出来,并且把 WorkerPipe 传递给 Worker 。这样就在 Sched 和 Worker 之间建立了一个 Pipe 。
3.Worker 在被 fork 出来之后,通常就阻塞在读 WorkerPipe 上面。Sched 通过 WorkerPipe 发送任务给 Worker 。
>>Worker 完成任务之后,通过 WorkerPipe 发送结果给 Sched 。Worker 可以不断地重复这个过程,这样就达到了一个池的效果。
4.对于使用 libevent 这类事件驱动类型的程序,这个进程池也能方便地被调用。
>>因为 Worker 曝露出来的是一个 PipeFd,能够方便地加入到 libevent 的事件循环中。这类事件驱动类的程序,
>>通常使用单线程实现,当具体的任务处理可能需要耗费比较长时间的时候,就需要使用多线程或者多进程来辅助了。
SPProcPool 提供了 3 个服务器框架:
SP_ProcInetServer(传递文件句柄)
SP_ProcLFServer(Leader/Follower模型)
SP_ProcMTServer(Worker模型)
它们的定义很类似:
class SP_ProcInetServer {
public:
SP_ProcInetServer( const char * bindIP, int port,
SP_ProcInetServiceFactory * factory );
virtual ~SP_ProcInetServer();
virtual int start();
};
class SP_ProcLFServer {
public:
SP_ProcLFServer( const char * bindIP, int port,
SP_ProcInetServiceFactory * factory );
virtual ~SP_ProcLFServer();
virtual int start();
};
class SP_ProcMTServer {
public:
SP_ProcMTServer( const char * bindIP, int port,
SP_ProcInetServiceFactory * factory );
virtual ~SP_ProcMTServer();
virtual int start();
};
从接口中可以看到,要使用这些框架,需要提供一个 SP_ProcInetServiceFactory 类的实例。这个类相关的定义如下:
class SP_ProcInetService {
public:
virtual ~SP_ProcInetService();
virtual void handle( int socketFd ) = 0;
};
class SP_ProcInetServiceFactory {
public:
virtual ~SP_ProcInetServiceFactory();
virtual SP_ProcInetService * create() const = 0;
virtual void workerInit( const SP_ProcInfo * procInfo );
virtual void workerEnd( const SP_ProcInfo * procInfo );
};
这里使用的是典型的抽象工厂方法。在工厂类中,除了 create 方法之外,还有两个特别的方法:workerInit 和 workerEnd 。workerInit 在子进程开始运行的时候被调用,workerEnd 在子进程退出的时候被调用。在 Service 类中,只有一个 handle 方法,它的参数就是已经 accept 到 socket 。
下面以一个简单的服务器为例进行说明。这个服务器是模仿《unix网络编程(第二版)》(中文版)第27章的服务器。服务器从 socket 读入包含一个数字的一行,然后根据这个数字返回相应的内容。
要实现这个简单的服务器例子,代码如下:
class SP_ProcUnpService : public SP_ProcInetService {
public:
SP_ProcUnpService() {}
virtual ~SP_ProcUnpService() {}
virtual void handle( int sockfd ) {
int ntowrite;
ssize_t nread;
char line[MAXLINE], result[MAXN];
for ( ; ; ) {
if ( (nread = read(sockfd, line, MAXLINE)) == 0) {
return; /* connection closed by other end */
}
/* line from client specifies #bytes to write back */
ntowrite = atol(line);
if ((ntowrite <= 0) || (ntowrite > MAXN)) {
syslog( LOG_WARNING, "WARN: client request for %d bytes", ntowrite);
exit( -1 );
}
SP_ProcPduUtils::writen(sockfd, result, ntowrite);
}
}
};
class SP_ProcUnpServiceFactory : public SP_ProcInetServiceFactory {
public:
SP_ProcUnpServiceFactory() {}
virtual ~SP_ProcUnpServiceFactory() {}
virtual SP_ProcInetService * create() const {
return new SP_ProcUnpService();
}
virtual void workerInit( const SP_ProcInfo * procInfo ) {
signal( SIGINT, SIG_DFL );
printf( "pid %d start\n", (int)procInfo->getPid() );
}
virtual void workerEnd( const SP_ProcInfo * procInfo ) {
printf( "pid %d exit, pipeFd %d, requests %d, lastActiveTime %ld\n",
(int)procInfo->getPid(), procInfo->getPipeFd(),
procInfo->getRequests(), procInfo->getLastActiveTime() );
}
};
int main( int argc, char * argv[] )
{
SP_ProcMTServer server( "", 1770, new SP_ProcUnpServiceFactory() );
server.start();
return 0;
}
- 11:30
- 浏览 (1038)
- 评论 (5)
- 分类: OpenSource项目
- 进入论坛
- 发布在 C++语言 圈子
- 相关推荐
评论
ken1984
2008-01-01
直接在内存操作比pipe快,shm也可以是匿名的。
seen
2007-12-27
iunknown 写道
seen 写道
多进程服务器,大量IO,少量CPU的情况下,
pipe跟shared memory比起来,效率哪个高?楼主有这方面经验不?
pipe跟shared memory比起来,效率哪个高?楼主有这方面经验不?
不是太明白你说的意思。
是说程序本身已经有大量的 IO ,现在又需要在进程间通信,那么是用 pipe 还是 shm (严格来说,用 shm 的话,还需要一个进程间同步的手段) ? 还是说,这个大量的 IO 就是进程间通信带来的 ?
关于各种进程间通信的手段,在 《UNIX网络编程 第2卷 进程间通信》这本书的附录中,给出了作者的测试结果,也给出了测试程序。针对具体的情况,可以跑一下这些测试程序。
嗯 就是说一个服务器面对的业务是大量IO 但是不需要什么计算
而这个服务器采用了多进程的模式 在这种情况下
父子进程之间需要通信 是shm快还是pipe快
shm是需要同步的
第二卷还没看过
iunknown
2007-12-27
seen 写道
多进程服务器,大量IO,少量CPU的情况下,
pipe跟shared memory比起来,效率哪个高?楼主有这方面经验不?
pipe跟shared memory比起来,效率哪个高?楼主有这方面经验不?
不是太明白你说的意思。
是说程序本身已经有大量的 IO ,现在又需要在进程间通信,那么是用 pipe 还是 shm (严格来说,用 shm 的话,还需要一个进程间同步的手段) ? 还是说,这个大量的 IO 就是进程间通信带来的 ?
关于各种进程间通信的手段,在 《UNIX网络编程 第2卷 进程间通信》这本书的附录中,给出了作者的测试结果,也给出了测试程序。针对具体的情况,可以跑一下这些测试程序。
seen
2007-12-26
多进程服务器,大量IO,少量CPU的情况下,
pipe跟shared memory比起来,效率哪个高?楼主有这方面经验不?
pipe跟shared memory比起来,效率哪个高?楼主有这方面经验不?
iunknown
2007-12-25
对比了传递文件句柄和 Leader/Follower 两种做法的性能,结果是 Leader/Follower 模型比传递文件句柄快。
采用的测试模型就是《Unix网络编程(第二版)》第27章的模型。对书上提到的 client 测试工具做了一下修改,加上了 client 端的时间测量。
测试的时候,两种框架都预先启动 10 个进程,并且 client 端也只跑 10 个进程,每个进程顺序发起 100 个请求,每次请求 512 字节。
针对每种框架,连续运行 client 10 次。每个 client 进程结束的时候,都输出它的总运行时间。最后要对比的就是进程的平均运行时间。
结果:
Leader/Follower 2635 / 100 = 26.35 (毫秒)
Descriptor Passing 3644 / 100 = 36.44 (毫秒)
简单来说,就是在 Leader/Follower 框架下,一个进程处理 100 个请求需要耗时 26 毫秒,而在另外一个框架下,需要 36 毫秒。
采用的测试模型就是《Unix网络编程(第二版)》第27章的模型。对书上提到的 client 测试工具做了一下修改,加上了 client 端的时间测量。
测试的时候,两种框架都预先启动 10 个进程,并且 client 端也只跑 10 个进程,每个进程顺序发起 100 个请求,每次请求 512 字节。
针对每种框架,连续运行 client 10 次。每个 client 进程结束的时候,都输出它的总运行时间。最后要对比的就是进程的平均运行时间。
结果:
Leader/Follower 2635 / 100 = 26.35 (毫秒)
Descriptor Passing 3644 / 100 = 36.44 (毫秒)
简单来说,就是在 Leader/Follower 框架下,一个进程处理 100 个请求需要耗时 26 毫秒,而在另外一个框架下,需要 36 毫秒。
发表评论
提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则
- 浏览: 79329 次

- 详细资料
搜索本博客
我的相册
vim
共 2 张
共 2 张
最近加入圈子
最新评论
-
集成 IOCP 到 Libevent
去找了 memcached for win32 的源代码,结果可以顺利编译,并且 ...
-- by iunknown -
集成 IOCP 到 Libevent
qiezi 写道GetQueuedCompletionStatusEx也支持超时 ...
-- by iunknown -
集成 IOCP 到 Libevent
GetQueuedCompletionStatusEx也支持超时,用它代替sel ...
-- by qiezi -
集成 IOCP 到 Libevent
linux/solaris上的aio有多种回调/通知方式,可能和libevent ...
-- by qiezi -
集成 IOCP 到 Libevent
SPServer是一个很不错的框架,受到启发,我用boost::asio实现了一 ...
-- by wow






评论排行榜