在C语言中,大部分时候都是单个进程的程序,如果想要获得较高的处理能力,较高的吞吐量,可以考虑多进程,进程间使用管道或共享内存进行通信。
本模型包括三个部分:
master 负责任务的生成、提交、执行结果接收、worker进程状态维护
worker 负责任务的接收、执行、执行结果反馈。
其他实用函数 包括mmap共有共享内存创建、任务队列维护、信号注册函数、检查进程是否存活等。
模型图简单描绘如下:
下面直接上代码:
1.master.c
#include
#include
#include
#include
#include
#include "task_info_c.h"
#include "cron.h"
#define MAX_WORKER_NUM 10
typedef struct worker_s{
int id;
pid_t pid;
int rfd; /* 读管道 */
int wfd; /* 写管道 */
char status; /* 0空闲 1忙碌 */
}worker_t; /* 工作进程信息结构体 */
static int g_worker_cnt=-1;
worker_t g_workers[MAX_WORKER_NUM]; /* 全局变量:工作进程信息数组 */
static fd_set g_fds; /* 读管道集 */
static int fd_p2c[2]; /* 父进程与子进程通信管道 */
static int fd_c2p[2]; /* 子进程与父进程通信管道 */
static int g_nMaxFd = -1; /* 最大管道描述字 */
static int loginit_flag = 0; /* 日志初始化标志 */
static int lock_flag=0;
static int queue_flag=0;
static pid_t enqueue_pid=0;
static int init_pipes(void); /* 生成两组管道 */
static void father_exit(); /* 父进程清理函数 */
static int create_worker(int i); /* 创建工作进程 */
static void father_SIGTERM(int sig);
extern void cron_enqueue();
void usage(char*p)
{
printf("%s -c 工作进程个数 -s 初始化动态库路径 !\n", p);
}
static char _libpath[300]={0};
int main(int argc, char**argv)
{
if(argc<2)
{
usage(argv[0]);
return -1;
}
int c;
while((c=getopt(argc,argv,"c:s:h"))!=-1)
{
switch(c)
{
case 'c':
g_worker_cnt=atoi(optarg);
break;
case 's':
strcpy(_libpath, optarg);
break;
case 'h':
usage(argv[0]);
exit(1);
default:
exit(1);
}
}
if(g_worker_cnt<=0)
{
printf("-c 参数错误 worker_cnt[%d]!\n", g_worker_cnt);
return -1;
}
else if(g_worker_cnt>MAX_WORKER_NUM)
{
printf("工作进程数目过多!最大%d,当前%d\n", MAX_WORKER_NUM,g_worker_cnt);
return -1;
}
pid_t pid;
int iRet;
int i;
char cBuf[COMM_LEN+1];
char cStderr[256]={0};
struct task_info_c sTask_info;
int iDealFlag=0;
fd_set rfds,efds;
char zlog_conf[300]={0};
pid=fork();
if(pid>0)
{
exit(1);
}
sprintf(cStderr, "%s/log/stderr_cronmaster", getenvinfo("CBWORK"));
sprintf(zlog_conf, "%s/etc/zlog_cronmaster.conf",getenvinfo("CBHOME"));
setenv("ZLOGF", "cronmaster", 1);
iRet = dzlog_init(zlog_conf, "cronmaster");
if(iRet)
{
logstderr("loginit error![%d]", iRet);
return -1;
}
logstderr("loginit ok!");
loginit_flag=1;
sig_register(SIGTERM,father_SIGTERM);
atexit(father_exit);
/* 初始化队列和保护锁 */
iRet = init_lock();
if(iRet)
{
vtcp_exit("队列锁创建失败!");
return -1;
}
lock_flag=1;
iRet = init_queue();
if(iRet)
{
logstderr("队列创建失败!");
return -1;
}
queue_flag=1;
logstderr("队列初始化成功!");
/* 创建队列维护进程 */
pid = fork();
if(pid==-1)
{
logstderr("fork error!");
return -1;
}
else if(pid==0)
{
/*iRet = execlp("cron_enqueue", "cron_enqueue", NULL);
if(iRet)
{
logstderr("cron_enqueue 启动失败!!");
exit(1);
}*/
cron_enqueue();
}
enqueue_pid = pid;
FD_ZERO(&g_fds);
/* 创建工作进程 */
for(i=0;i0)
{
for(i=0; i
这是程序的主体部分:
接收两个命令行参数,-c 表示worker进程的数目,-s表示需要运行的动态库路径。
首先使用zlog初始化了一下日志环境。注意,zlog里面会设置SIGTERM信号的信号处理函数,所以需要在dzlog_init之后,设置我们自己的信号处理函数。
程序中维护了一个任务队列,及线程锁,代码如下:
任务队列:保存最多N个任务,可自行设置。
static loop_queue_t *q;
int init_queue()
{
int fd;
fd = open("/dev/zero", O_RDWR);
q = (loop_queue_t*)mmap(NULL, sizeof(loop_queue_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(!q)
{
return -1;
}
memset(q, 0x00, sizeof(loop_queue_t));
q->front=0;
q->rear=0;
return 0;
}
int get_queue_length()
{
return (q->rear - q->front + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE;
}
int get_from_queue(struct task_info_c*task)
{
int len=get_queue_length();
if(0==len)
{
return -1;
}
memcpy(task, q->base+q->front, sizeof(struct task_info_c));
q->front = (q->front+1)%MAX_QUEUE_SIZE;
return 0;
}
int add_to_queue(struct task_info_c task)
{
int len=get_queue_length();
if(len >= MAX_QUEUE_SIZE-1)
{
return -1;
}
memcpy(q->base+q->rear, &task, sizeof(struct task_info_c));
q->rear=(q->rear+1)%MAX_QUEUE_SIZE;
return 0;
}
int del_queue()
{
return munmap(q, sizeof(loop_queue_t));
}
线程锁:保证任务队列 同一时间只能被一个进程读写。
static pthread_mutex_t *_lock;
int init_lock()
{
int fd;
pthread_mutexattr_t mattr;
fd = open("/dev/zero", O_RDWR);
_lock = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(_lock, &mattr);
return(0);
}
void wait_lock()
{
pthread_mutex_lock(_lock);
}
void release_lock()
{
pthread_mutex_unlock(_lock);
}
int del_lock()
{
int ret = munmap(_lock, sizeof(pthread_mutex_t));
return(ret);
}
master.c中cron_enqueue()是我的任务生成进程,代码大体为:
wait_lock();
iRet = add_to_queue(/*你的task结构体*/);
release_lock();
里面包含循环的生成任务或扫描任务的逻辑,具体由业务逻辑决定。
在master.c的大while循环里,主要做了3件事:
a.从任务队列取任务
b.将任务派发给某个空闲worker
c.接收子进程执行结果反馈并更新worker状态
2.worker.c
#include
#include
#include
#include
#include
#include
#include
#include "logapi.h"
#include "envinfo.h"
#include "cbbase.h"
#include "cbdb.h"
#include "cron.h"
#include "task_info_c.h"
int rfd;
int wfd;
static int db_open_flag = 0; /* 数据库打开标志 */
static int loginit_flag = 0; /* 日志初始化标志 */
void work_SIGTERM(int sig);
void work_exit0();
int work_init0(char*);
struct task_info_c g_task_info;
void *handle = NULL;
int (*work_init)()=NULL;
int (*work_exit)()=NULL;
int (*work_do)(struct task_info_c*)=NULL;
int main(int argc, char* argv[])
{
int iRet;
char cBuf[COMM_LEN+1];
rfd=atoi(argv[1]);
wfd=atoi(argv[2]);
logstderr("子进程 rfd %d wfd %d", rfd, wfd);
memset(cBuf, 0x00, sizeof(cBuf));
read(rfd, cBuf, COMM_LEN);
logstderr("子进程read[%s]", cBuf);
if(strcmp(cBuf, "GET_READY")==0)
{
iRet=work_init0(argv[3]);
if(!iRet)
{
strcpy(cBuf, "ALREADY");
}
else
{
strcpy(cBuf, "NOTREADY");
}
write(wfd, cBuf, COMM_LEN);
}
sig_register(SIGTERM,work_SIGTERM);
atexit(work_exit0);
logstderr("worker init ok!");
fflush(stderr);
while(1)
{
memset(&g_task_info, 0x00, sizeof(g_task_info));
read(rfd, &g_task_info, sizeof(g_task_info));
write(wfd, "FETCH_OK", COMM_LEN);
/* do task */
iRet = sql_begin();
iRet = (*work_do)(&g_task_info);
if(iRet==0)
{
write(wfd, "DEAL_SUCCESS", COMM_LEN);
iRet = sql_commit();
if(iRet) vtcp_exit("提交数据库失败!");
}
else
{
write(wfd, "DEAL_FAILED", COMM_LEN);
iRet = sql_rollback();
if(iRet) vtcp_exit("回滚数据库失败!");
}
}
}
int work_init0(char*libpath)
{
int iRet;
char cStderr[256]={0};
char cZlogConf[300];
sprintf(cStderr, "%s/log/stderr_cronworker", getenvinfo("CBWORK"));
sprintf(cZlogConf, "%s/etc/zlog_cronworker.conf",getenv("CBHOME"));
setenv("ZLOGF", "cronworker", 1);
iRet = dzlog_init(cZlogConf, "cronworker");
if(iRet)
{
logstderr("loginit error![%d]", iRet);
return -1;
}
logstderr("loginit ok!");
loginit_flag=1;
iRet = db_open();
if(iRet)
{
logstderr("db_open 失败!");
exit(1);
}
logstderr("db_open ok!");
db_open_flag=1;
handle = dlopen(libpath, RTLD_NOW|RTLD_LOCAL);
if(!handle)
{
logstderr("dlopen error![%s][%s]", libpath, dlerror());
return -1;
}
work_init = (int(*)())dlsym(handle, "work_init");
if(!work_init)
{
logstderr("work_init undefined![%s]", libpath);
return -1;
}
work_do = (int(*)())dlsym(handle, "work_do");
if(!work_do)
{
logstderr("work_do undefined![%s]", libpath);
return -1;
}
work_exit = (int(*)())dlsym(handle, "work_exit");
if(!work_exit)
{
logstderr("work_exit undefined![%s]", libpath);
return -1;
}
iRet = (*work_init)();
if(iRet)
{
logstderr("work_init error![%d]", iRet);
return -1;
}
freopen(cStderr, "a" , stderr);
return 0;
}
void work_SIGTERM(int sig)
{
exit(1);
}
void work_exit0()
{
close(rfd);
close(wfd);
if(work_exit) (*work_exit)();
if(handle) dlclose(handle);
if(db_open_flag)
{
db_close();
}
if(loginit_flag)
{
zlog_fini();
}
}
worker进程首先根据传入的动态库确定任务执行的入口,然后就进入到while循环,在循环中,干了3件事:
a.接收任务
b.执行任务
c.反馈任务执行结果
3.其他代码片段
init_pipes() 初始化master和worker父子进程之间的通信管道。
static int init_pipes(void)
{
if( pipe(fd_p2c) < 0 )
{
vtcp_exit("pipe failed:p2c:[%s]", strerror(errno) );
return (-1);
}
if( pipe(fd_c2p) < 0 )
{
vtcp_exit(" pipe failed:c2p: [%s]", strerror(errno) );
return (-1);
}
return (0);
}
create_worker():master创建worker
static int create_worker(int i)
{
pid_t pid;
char cBuf[COMM_LEN+1];
char cRfd[20];
char cWfd[20];
int iRet;
memset(cBuf, 0x00, sizeof(cBuf));
pid=fork();
if(pid==-1)
{
logstderr("fork失败!i[%d]\n",i);
return -1;
}
else if(pid==0)
{
sprintf(cRfd, "%d", fd_p2c[0]);
sprintf(cWfd, "%d", fd_c2p[1]);
logstderr("启动子进程 rfd %s, wfd %s\n",cRfd, cWfd);
iRet=execlp("cron_worker", "cron_worker", cRfd, cWfd, _libpath, NULL);
if(iRet)
{
logstderr("execlp failed![%d][%s]", errno, strerror(errno));
zlog_fini();
exit(1);
}
}
else
{
g_workers[i].id=i;
g_workers[i].pid=pid;
g_workers[i].rfd=fd_c2p[0];
g_workers[i].wfd=fd_p2c[1];
g_workers[i].status='0';
if(g_workers[i].rfd>g_nMaxFd)
{
g_nMaxFd=g_workers[i].rfd;
}
FD_SET(g_workers[i].rfd, &g_fds);
write(g_workers[i].wfd, "GET_READY", COMM_LEN);
read(g_workers[i].rfd, cBuf, COMM_LEN);
if(strcmp(cBuf, "ALREADY")==0)
{
logstderr("工作进程[%d] 准备好!", g_workers[i].pid);
}
else
{
logstderr("工作进程[%d] 未准备好!", g_workers[i].pid);
return -1;
}
}
return 0;
}
父进程信号处理函数:
static void father_SIGTERM(int sig)
{
exit(1);
}
father_exit()负责master退出时子进程的清理。
static void father_exit()
{
int i;
freopen("/dev/tty", "a", stderr);
for(i=0;i0 || checkpid(g_workers[i].pid)!=-1)
{
logstderr("杀死进程[%d]\n", g_workers[i].pid);
kill(g_workers[i].pid, SIGTERM);
close(g_workers[i].rfd);
close(g_workers[i].wfd);
}
}
if(enqueue_pid)
{
logstderr("杀死队列进程![%d]", enqueue_pid);
kill(enqueue_pid, SIGTERM);
}
if(queue_flag)
{
del_queue();
}
if(lock_flag)
{
del_lock();
}
if(loginit_flag)
{
zlog_fini();
}
}
sig_register信号注册函数:
int sig_register(int sig, void(*handler)(int))
{
struct sigaction act;
memset(&act,0,sizeof(struct sigaction));
act.sa_handler=handler;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
if(sigaction(sig,&act,NULL)==-1)
{
vtcp_exit("sigaction error![%s]",strerror(errno));
return(-1);
}
return 0;
}
进程状态检测函数:
int checkpid(pid_t pid)
{
if (pid <= 0)
{
return -1;
}
#ifdef HPUX
int result = 0;
struct pst_status psta;
memset(&psta, 0x00, sizeof(psta));
result = pstat_getproc(&psta, sizeof(psta), (size_t)0, pid);
if (result < 0)
{
return -1;
}
else
{
if (psta.pst_stat == PS_ZOMBIE)
{
return -1;
}
else
{
return SW_OK;
}
}
return 0;
#else
FILE *fp = NULL;
char filename[128];
memset(filename, 0x0, sizeof(filename));
sprintf(filename, "/proc/%d/status", pid);
fp = fopen(filename, "rb");
if (fp == NULL)
{
if (errno == ENOENT)
{
errno = 0;
return -1;
}
else
{
return 0;
}
}
fclose(fp);
return 0;
#endif
}
4.最后
这只是一个简单的框架,希望能给大家提供一点帮助。这里通信选用的是管道,当然使用
其他方式,如socket_pair、命名管道、消息队列、共享内存都是可以的。如果想扩展到多机运行,需要借助网络进行协调,可以自己使用socket通信、zeremq的PUSH/POLL模型等方式进行实现,zlog和zeremq放在以后的文章中进行讲解。