c语言进程池原理及实现

进程池与线程池出发点一样,都是考虑多核情况下任务的并行处理;
从多进程和多线程编程的区别上看,多线程有许多的同步、互斥的方法,较擅长于异步协作;而多进程同步、互斥的方法相对比较麻烦,则更多地考虑上下文独立执行;
从Nginx使用线程池/进程池处理大并发的思路去分析,其实就是多客户端大量连接的场景;主进程监听是否有新客户端tcp连接,然后分发给工作进程去响应http请求,在这种场景下每个连接都是一个独立的上下文逻辑,每个工作进程的内容都是对等地处理http请求,这种情况就非常适合进程池的方式;
 

把上述的场景给抽象出来,关注于父子进程的通讯,也就是下图流程:

Master主进程给Worker子进程分发任务,通讯的方式用的就是单向管道的方式;

分发任务这块,可以有随机分发、轮流分发、计分板等多种方法,具体可以结合业务进行设计;

本节根据上述流程图进行一个简单的实现,假设分别有A、B、C三种任务,主进程使用轮流分发(Round-robin)把任务均匀地分配给子进程们;

在结构体process上得考虑一下,Master进程需要保存所有子进程的进程号、管道号;

 

/* Process struct */
typedef struct process
{
        char name[SIZE_NAME_NORMAL];                    /* Process name */
        pid_t pid;                                      /* Process id */
        int pipefd[2];                                  /* Connection between master/slave */
        size_t score;                                   /* Score record */
} process_t;
 
/* Program instance */
typedef struct instance
{
        char prg_name[SIZE_NAME_LONG];                  /* Program name with path */
        char cfg_name[SIZE_NAME_LONG];                  /* Configure name with path */
 
        u16 process_num;                                /* Sub process number */
        u16 process_idx;                                /* Current process index */
 
        struct process *proc;                           /* Process struct */
} instance_t;

进程池的初始化函数,根据给定的Worker进程数开启子进程;

 

int process_pool(instance_t *pinst, u16 process_num)
{
    int ret = FAILURE;
    int ix  = 0;
    int status = 0;
 
    if ( !pinst || !process_num ) {
        printf("NULL\n");
        goto _E1;
    }
 
    signal(SIGINT,  __sig_quit);
    signal(SIGTERM, __sig_quit);
 
    pinst->process_idx = 0;
    pinst->process_num = process_num;
    pinst->proc = (process_t *)calloc(process_num + 1, sizeof(process_t));
    if ( !pinst->proc ) {
        printf("Alloc process pool struct failed\n");
        goto _E1;
    }
 
    for ( ix = 1; ix <= process_num; ix++ ) {
 
        int bufsize = 1;
        ret = pipe(pinst->proc[ix].pipefd);
        if ( SUCCESS != ret ) {
            printf("socketpair\n");
            goto _E2;
        }
 
        printf("Setup worker#%u\n", ix);
 
        pinst->proc[ix].pid = fork();
        if ( pinst->proc[ix].pid < 0 ) {
            printf("fork\n");
            goto _E2;
        }
        else if ( pinst->proc[ix].pid > 0 ) {
            /* Father */
            CLOSE_FD(pinst->proc[ix].pipefd[0]);
            continue;
        }
        else {
            /* Child */
            CLOSE_FD(pinst->proc[ix].pipefd[1]);
            pinst->process_idx = ix;
            ret = __worker(pinst);
            goto _E2;
        }
    }
 
    ret = __master(pinst);
 
    /* Waiting workers */
    for ( ix = 1; ix <= pinst->process_num; ix++ ) {
        waitpid(pinst->proc[ix].pid, &status, WNOHANG);
    }
 
_E2:
    for ( ix = 1; ix <= pinst->process_num; ix++ ) {
        CLOSE_FD(pinst->proc[ix].pipefd[1]);
        CLOSE_FD(pinst->proc[ix].pipefd[0]);
    }
 
    FREE_POINTER(pinst->proc);
_E1:
    return ret;
}

然后就是Master Worker的实现,Master就是简单地进行一个事件分发,通过约定的协议"ABC"代表三种工作命令,“Q“代表退出进程;

 

static int __master(instance_t *pinst)
{
    int ret  = 0;
    int fd   = 0;
    int ix   = 0;
    int roll = 0;
 
    char c = 0;
 
#define __offset(pinst) ((pinst)->proc[(pinst)->process_idx])
#define __round_robin(pinst, roll) \
    ((pinst)->proc[((roll) % (pinst)->process_num) + 1].pipefd[1])
 
    printf("Master#%u setup\n", pinst->process_idx);
 
    for ( g_enable = 1; g_enable; ) {
 
        /* Get pipe fd by round-robin */
        fd = __round_robin(pinst, ++roll);
 
        c = 'A' + roll % 3; // 'A'/'B'/'C'
        ret = write(fd, &c, 1);
        if ( ret <= 0 ) {
            return FAILURE;
        }
        sleep(1);
    }
 
    /* Tell all workers to quit */
    for ( ix = 1; ix <= pinst->process_num; ix++ ) {
        c = 'Q';
        write(__round_robin(pinst, ++roll), &c, 1);
    }
 
    printf("Master#%u shutdown\n", pinst->process_idx);
    return SUCCESS;
}
static int __worker(instance_t *pinst)
{
    int fd = __offset(pinst).pipefd[0];
    int ix = 0;
    ssize_t read_byte = FAILURE;
    char buffer[1024] = {0};
 
    printf("Worker#%u setup\n", pinst->process_idx);
    for ( g_enable = 1; g_enable; ) {
        read_byte = read(fd, buffer, sizeof(buffer));
        if ( read_byte <= 0 ) {
            if ( errno == EAGAIN || errno == EINTR ) {
                continue;
            }
            return FAILURE;
        }
 
        for ( ix = 0; ix < read_byte; ix++ ) {
            switch ( buffer[ix] ) {
                case 'A':
                case 'B':
                case 'C':
                    __offset(pinst).score += buffer[ix];
                    printf("Worker#%u Recv command: %c, score: %llu\n",
                            pinst->process_idx,
                            buffer[ix], __offset(pinst).score);
                    break;
 
                case 'Q':
                    printf("Quit\n");
                    g_enable = 0;
                    break;
 
                default:
                    break;
            }
        }
    }
 
    printf("Worker#%u shutdown\n", pinst->process_idx);
    return SUCCESS;
}

 

最后带一个main方法,考虑了进程释放的问题,在这个demo中使用信号对genable进行控制;

 

static u8 g_enable;                         /* Running flag */
static void __sig_quit(int sig)
{
    g_enable = 0;
}
 
 
int main(int argc, char *argv[])
{
    instance_t inst = {0};
 
    if ( argc < 2 ) {
        printf("Usage: \n\t%s < process number >\n", argv[0]);
        return EXIT_FAILURE;
    }
 
    return process_pool(&inst, atoi(argv[1]));
}

 

完整代码如下:

/***
 *根据上述流程图进行一个简单的实现,假设分别有A、B、C三种任务,主进程使用轮流分发(Round-robin)把任务均匀地分配给子进程们;
 *在结构体process上得考虑一下,Master进程需要保存所有子进程的进程号、管道号;
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>


#define FAILURE -1
#define u8  unsigned char

#define u16 unsigned  short

#define SIZE_NAME_NORMAL 48

#define SIZE_NAME_LONG 48

#define SUCCESS 0

#define CLOSE_FD(x) (close(x))

#define FREE_POINTER(x) (free(x))
#define __offset(pinst) ((pinst)->proc[(pinst)->process_idx])

/* 写管道*/
#define __round_robin(pinst, roll) \
    ((pinst)->proc[((roll) % (pinst)->process_num) + 1].pipefd[1])
/***
 *本节根据上述流程图进行一个简单的实现,假设分别有A、B、C三种任务,主进程使用轮流分发(Round-robin)把任务均匀地分配给子进程们;
 *在结构体process上得考虑一下,Master进程需要保存所有子进程的进程号、管道号;
 *
 */

/* Process struct */
typedef struct process
{
    char name[SIZE_NAME_NORMAL];                    /* Process name */
    pid_t pid;                                      /* Process id */
    int pipefd[2];                                  /* Connection between master/slave */
    size_t score;                                   /* Score record */
} process_t;

/* Program instance */
typedef struct instance
{
    char prg_name[SIZE_NAME_LONG];                  /* Program name with path */
    char cfg_name[SIZE_NAME_LONG];                  /* Configure name with path */

    u16 process_num;                                /* Sub process number */
    u16 process_idx;                                /* Current process index */

    struct process *proc;                           /* Process struct */

} instance_t;


static u8 g_enable;                         /* Running flag */

static void __sig_quit(int sig)
{
    g_enable = 0;
}


static int __master(instance_t *pinst)
{
    int ret  = 0;
    int fd   = 0;
    int ix   = 0;
    int roll = 0;

    char c = 0;


    printf("Master#%u setup\n", pinst->process_idx);

    for ( g_enable = 1; g_enable; ) {

        /* Get pipe fd by round-robin */
        fd = __round_robin(pinst, ++roll);

        c = 'A' + roll % 3; // 'A'/'B'/'C'
        ret = write(fd, &c, 1);
        if ( ret <= 0 ) {
            return FAILURE;
        }
        sleep(1);
    }

    /* Tell all workers to quit */
    for ( ix = 1; ix <= pinst->process_num; ix++ ) {
        c = 'Q';
        write(__round_robin(pinst, ++roll), &c, 1);
    }

    printf("Master#%u shutdown\n", pinst->process_idx);
    return SUCCESS;
}


static int __worker(instance_t *pinst)
{
    int fd = __offset(pinst).pipefd[0];
    int ix = 0;
    ssize_t read_byte = FAILURE;
    char buffer[1024] = {0};

    printf("Worker#%u setup\n", pinst->process_idx);

    for ( g_enable = 1; g_enable; ) {
        printf("work read front......\n");
        read_byte = read(fd, buffer, sizeof(buffer)); /* 读管道会阻塞*/
        printf("work read after......\n");
        if ( read_byte <= 0 ) {
            if ( errno == EAGAIN || errno == EINTR ) {
                continue;
            }
            return FAILURE;
        }

        for ( ix = 0; ix < read_byte; ix++ ) {
            switch ( buffer[ix] ) {
                case 'A':
                case 'B':
                case 'C':
                    __offset(pinst).score += buffer[ix];
                    printf("Worker#%u Recv command: %c, score: %llu\n",
                           pinst->process_idx,
                           buffer[ix], __offset(pinst).score);
                    break;

                case 'Q':
                    printf("Quit\n");
                    g_enable = 0;
                    break;

                default:
                    break;
            }
        }
    }

    printf("Worker#%u shutdown\n", pinst->process_idx);
    return SUCCESS;
}



int process_pool(instance_t *pinst, u16 process_num)
{
    int ret = FAILURE;
    int ix  = 0;
    int status = 0;

    if ( !pinst || !process_num ) {
        printf("NULL\n");
        goto _E1;
    }
    /**
     * SIGINT ctrl+c 终止进程
     */
    signal(SIGINT,  __sig_quit);
    /** SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和
     *处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这
     *个信号. */

    signal(SIGTERM, __sig_quit);

    pinst->process_idx = 0;

    pinst->process_num = process_num;

    pinst->proc = (process_t *)calloc(process_num + 1, sizeof(process_t));

    if ( !pinst->proc ) {
        printf("Alloc process pool struct failed\n");
        goto _E1;
    }

    for ( ix = 1; ix <= process_num; ix++ ) {

        int bufsize = 1;
        /*fd[0]:读管道,fd[1]:写管道*/
        ret = pipe(pinst->proc[ix].pipefd);

        if ( SUCCESS != ret ) {
            printf("socketpair\n");
            goto _E2;
        }

        printf("Setup worker#%u\n", ix);

        pinst->proc[ix].pid = fork();

        if ( pinst->proc[ix].pid < 0 ) {
            printf("fork\n");
            goto _E2;
        }

        else if ( pinst->proc[ix].pid > 0 ) {
            /* Father process close read fd pipe */
            CLOSE_FD(pinst->proc[ix].pipefd[0]);
            printf(".......\n");
            continue;
        }
        else {
            /* Child process close write fd pipe */
            CLOSE_FD(pinst->proc[ix].pipefd[1]);
            printf("xxxxxxxx\n");
            pinst->process_idx = ix;
            ret = __worker(pinst);
            goto _E2;
        }
    }

    ret = __master(pinst);

    /* Waiting workers */
    for ( ix = 1; ix <= pinst->process_num; ix++ ) {
        waitpid(pinst->proc[ix].pid, &status, WNOHANG);
    }

    _E2:
    for ( ix = 1; ix <= pinst->process_num; ix++ ) {
        CLOSE_FD(pinst->proc[ix].pipefd[1]);
        CLOSE_FD(pinst->proc[ix].pipefd[0]);
    }

    FREE_POINTER(pinst->proc);
    _E1:
    return ret;
}


int main(int argc, char *argv[])
{
    instance_t inst = {0};  /*实例结构体*/

    if ( argc < 2 ) {
        printf("Usage: \n\t%s < process number >\n", argv[0]);
        return EXIT_FAILURE;
    }

    return process_pool(&inst, atoi(argv[1]));
}

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值