C语言多进程任务执行框架

 

在C语言中,大部分时候都是单个进程的程序,如果想要获得较高的处理能力,较高的吞吐量,可以考虑多进程,进程间使用管道或共享内存进行通信。

本模型包括三个部分:

master 负责任务的生成、提交、执行结果接收、worker进程状态维护

worker 负责任务的接收、执行、执行结果反馈。

其他实用函数 包括mmap共有共享内存创建、任务队列维护、信号注册函数、检查进程是否存活等。

模型图简单描绘如下

下面直接上代码:

1.master.c

#include <envinfo.h>
#include <cbdb.h>
#include <cbbase.h>
#include <time.h>
#include <sys/wait.h>
#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;i<g_worker_cnt;i++)
    {
        init_pipes();
        iRet = create_worker(i);
        if(iRet)
        {
            logstderr("create_worker 失败![%d]", i);
            exit(1);
        }
        sleep(1);
    }
    logstderr("工作进程建立完成!");
    
    freopen(cStderr, "a" , stderr);
    
    /* 分发 */
    while(1)
    {
        wait_lock();
        iRet = get_from_queue(&sTask_info);
        release_lock();
        if(iRet)
        {
            pub_time_timer(0, 100000); /* 等待0.1秒 */
            continue;
        }
        vtcp_info("打印下任务信息!");
        Task_info_Debug(&sTask_info);
        
WAIT:
        for(i=0; i<g_worker_cnt; i++)
        {
            if(g_workers[i].pid==-1 ||checkpid(g_workers[i].pid)==-1|| g_workers[i].status=='0')
            {
                break;
            }
        }
        if(i<g_worker_cnt) /* 有可用子进程 */
        {
            /* 重启死亡或管道破裂的子进程 */
            if(g_workers[i].pid==-1 || checkpid(g_workers[i].pid)==-1)
            {
                logstderr("重启进程 i[%d] pid[%d]", i, g_workers[i].pid);
                fflush(stderr);
                init_pipes();
                iRet = create_worker(i);
                if(iRet)
                {
                    logstderr("create_worker 失败![%d]", i);
                    exit(1);
                }
            }
            
            write(g_workers[i].wfd, &sTask_info, sizeof(sTask_info));
            memset(cBuf, 0x00, sizeof(cBuf));
            read(g_workers[i].rfd, cBuf, COMM_LEN);
            if(strcmp(cBuf, "FETCH_OK")!=0)
            {
                vtcp_exit("工作进程[%d]接收任务失败[%s]!", g_workers[i].pid, cBuf);
            }
            else
            {
                vtcp_exit("工作进程[%d]接收任务成功![%s]", g_workers[i].pid, cBuf);
                g_workers[i].status='1';
            }
            iDealFlag=1;
        }
        
        /* 等待可能的任务完成 */
        struct timeval tv;
        tv.tv_sec=0;
        tv.tv_usec=10000; /* wait 10ms */
        rfds=g_fds;
        efds=g_fds;
        iRet=select(g_nMaxFd+1,&rfds,NULL,&efds,&tv);
        if(iRet < 0)
        {
            vtcp_exit("select fail! [%s]", strerror(errno) );
            return (-1);
        }
        else if(iRet>0)
        {
            for(i=0; i<g_worker_cnt; i++)
            {
                if(FD_ISSET(g_workers[i].rfd, &rfds))
                {
                    memset(cBuf, 0x00, sizeof(cBuf));
                    read(g_workers[i].rfd, cBuf, COMM_LEN);
                    if(!strcmp(cBuf, "DEAL_SUCCESS"))
                    {
                        vtcp_exit("执行成功!pid[%d][%s]", g_workers[i].pid, cBuf);
                    }
                    else
                    {
                        vtcp_exit("执行失败!pid[%d][%s]", g_workers[i].pid, cBuf);
                    }
                    g_workers[i].status='0';
                    
                    /* 更新数据库状态 TODO*/
                }
                else if(FD_ISSET(g_workers[i].rfd, &efds))
                {
                    vtcp_exit("管道断裂![%d][%d]", i, g_workers[i].pid);
                    kill(g_workers[i].pid, SIGTERM);
                    int status;
                    waitpid(g_workers[i].pid, &status, 0); /* 等待子进程结束 */
                    FD_CLR(g_workers[i].rfd,&g_fds);
                    close(g_workers[i].rfd);
                    close(g_workers[i].wfd);
                    g_workers[i].pid=-1; /* 等待重启 */
                    g_workers[i].status='9';
                }
            }
        }
        if(!iDealFlag)
        {
            goto WAIT;
        }
        continue;
    }
}

这是程序的主体部分:

接收两个命令行参数,-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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/time.h>
#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;i<g_worker_cnt;i++)
    {
        if(g_workers[i].pid>0 || 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放在以后的文章中进行讲解。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值