Linux C一个简单的线程池示例: 固定数目线程池

1. 目标

Linux C编写一个简单的固定数目的线程池, 实现向线程池添加任务并执行的功能.

2. 固定数目线程池fixedthreadpool

类似于java的newFixedThreadPool, 线程池中的子线程数目固定, 在创建时由用户, 可以用一个线程队列来描述. 当用户需要用线程池执行任务时, 可以将作业加入等待队列, 如果线程队列有可用线程, 就从作业等待队列出队, 加入线程队列末尾. 这样确保, 任务执行的FIFO特性.
用双向循环链表实现队列, 保证FIFO特性同时, 确保高执行效率.
参考: Java线程池

要实现的固定数目线程池模型, 如下图所示:

3. 优缺点分析

优点:
1)线程池线程数目固定, 可以实现一定并发量, 同时又不会因为插入用户作业数过多而导致线程数目爆炸, 以至于服务器运行效率反而降低;
2)较通用的线程池模型, 可以应对大多数需要线程池运行用户作业的场景;
3)支持的用户作业数量无限, 不会丢失用户作业;
4)FIFO方式运行用户作业, 所有作业都能得到执行;
5)适合短小, 特别一次运行的用户作业任务;

缺点:
1)频繁创建用户作业节点, 效率低下, 而且可能导致内存碎片问题;
解决办法: 固定一个数目的用户作业队列数量, 只有超出限制时, 才新建节点用于新作业存储;

2)将评估线程数目的难题交给了用户, 不仅不友好, 而且存在使用不当的风险: 数目过大, 会浪费资源; 数目过小, 无法达到高效率并发运行作业的目的.
解决办法: 参考java的newCachedThreadPool, 维护一个固定数目的线程队列(工作队列), 然后超限值时, 才新建新的工作节点; 小于限值时, 回收空闲节点.

3)不适用于长期运行而不退出的作业任务, 否则其他线程长期无法得到执行, 而且线程池可能无法销毁;

4)不适用于需要优先执行的作业的场景;

参见java四种线程池的使用

4. 实现

fixedthreadpool.h

#include <pthread.h>

typedef void *(*jobcallback_t)(void *);

struct fixedthreadpool;

/**
 * 工作节点, 存放正在执行的线程信息
 * @note 每个节点对应一个线程
 */
typedef struct worker {
    pthread_t tid;                     /* 线程标识符 */
    struct fixedthreadpool *poolptr;   /* 归属线程池 */

    struct worker *prev;
    struct worker *next;
}nWorker_t;

/**
 * 工作队列
 * @note 执行任务的线程工作队列, 用双向循环链表实现, 每个节点是一个工作节点.
 * 工作队列的每个节点都是正在执行的作业, 而作业队列中的作业都是等待执行的.
 */
typedef struct workerqueue {
    nWorker_t *front;
    nWorker_t *rear;
}nWorkerQueue_t;

/**
 * 作业节点, 存放用户要执行的任务
 * @note 每个节点对应一个用户任务. 用双向循环链表节点, 实现该队列.
 */
typedef struct job {
    jobcallback_t job_func; /* 作业处理函数 */
    void *user_data;        /* 用户数据: 作业处理函数参数 */

    struct job *prev;
    struct job *next;
}nJob_t;

/**
 * 作业队列
 * @note 等待执行的用户作业队列, 用双向循环链表实现.
 * 工作队列的每个节点都是正在执行的作业, 而作业队列中的作业都是等待执行的.
 */
typedef struct jobqueue {
    nJob_t *front;
    nJob_t *rear;
}nJobQueue_t;

/**
 * 固定线程数目的线程池
 * @note 管理作业队列和工作队列
 */
typedef struct fixedthreadpool {
    nWorkerQueue_t workerqueue; /* 工作队列 */
    nJobQueue_t jobqueue;       /* 作业队列 */

    pthread_mutex_t mutex;      /* 互斥量 */
    pthread_cond_t notempty;    /* 条件变量: 作业队列非空 */

    int close;                  /* 关闭状态 */
}nFixedThreadPool;

int fixedthreadpool_create(nFixedThreadPool **poolpptr, int num);
int fixedthreadpool_addjob(struct fixedthreadpool *poolptr, jobcallback_t callback, void *arg);
void fixedthreadpool_destroy(struct fixedthreadpool *poolptr);

fixedthreadpool.c

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "fixedthreadpool.h"

/**
 * 线程池中每个线程对应的线程函数
 * @param arg [in] 线程参数
 * @note 线程函数的任务是, 当有空闲线程时, 就从等待执行的作业队列jobqueue取出用户作业job, 放入线程函数执行.
 * 每个线程同一时刻, 只能执行一个作业任务.
 */
static void *worker_callback(void *arg) {
    nWorker_t *worker= (nWorker_t *)arg;
    nFixedThreadPool *poolptr = worker->poolptr;

    while (1) {
        pthread_mutex_lock(&poolptr->mutex);

        /* 等待线程非空条件 */
        while (poolptr->jobqueue.front == NULL || poolptr->close == 0) {
            printf("thread %ld is waiting\n", pthread_self());
            pthread_cond_wait(&poolptr->notempty, &poolptr->mutex);
        }

        /* 线程池关闭时, 释放锁并退出当前线程 */
        if (poolptr->close == 1) {
            pthread_mutex_unlock(&poolptr->mutex);
            printf("thread %ld will exit\n", pthread_self());
            pthread_exit(NULL);
        }

        /* jobqueue弹出队头节点 */
        nJob_t *job = NULL;
        if (poolptr->jobqueue.front == poolptr->jobqueue.rear) {
            /* jobqueue只有一个节点 */
            job = poolptr->jobqueue.front;

            poolptr->jobqueue.front = poolptr->jobqueue.rear = NULL;
        }
        else {
            /* jobqueue 拥有超过1个节点 */
            job = poolptr->jobqueue.front;
            job->next = NULL;
            job->prev = NULL;

            poolptr->jobqueue.front = poolptr->jobqueue.front->next;
            poolptr->jobqueue.front->prev = poolptr->jobqueue.rear;
            poolptr->jobqueue.rear->next = poolptr->jobqueue.front;
        }

        pthread_mutex_unlock(&poolptr->mutex);
        pthread_cond_broadcast(&poolptr->notempty);

        /* 执行用户作业, 这里要求用户作业不能是无限循环, 不退出的作业, 否则线程无法正常结束, 从而导致线程池无法正常释放 */
        if (job)
            job->job_func(job->user_data);

        /* 释放用户作业节点 */
        if (job) {
            free(job);
            job = NULL;
        }
    }
}

/**
 * 创建指定线程数目的线程池
 * @param poolpptr [out] 指向线程池地址的指针
 * @param num 线程池线程数目
 * @return 创建结果
 * - < 0 创建失败
 * - 0 创建成功
 */
int fixedthreadpool_create(nFixedThreadPool **poolpptr, int num) {
    int i, ret;
    nFixedThreadPool *poolptr;

    if (poolpptr == NULL) return -1;
    /* 创建并验证线程池结构 */
    *poolpptr = (nFixedThreadPool *)malloc(sizeof (nFixedThreadPool));
    poolptr = *poolpptr;
    if (poolptr == NULL) {
        perror("malloc threadpool error");
        return -1;
    }

    /* 线程池线程数不能少于1个 */
    if (num < 1) num = 1;
    printf("fixedthreadpool_create, num = %d\n", num);

    /* 清0线程池结构 */
    memset(poolptr, 0, sizeof (nFixedThreadPool));

    /* 初始化 close */
    poolptr->close = 0;

    /* 初始化 mutex */
    pthread_mutex_init(&poolptr->mutex, NULL);

    /* 初始化条件变量 notempty */
    pthread_cond_init(&poolptr->notempty, NULL);

    /* 初始化工作队列 workerqueue : 按FIFO方式加入n个工作节点, 每个节点保存了线程标识符等信息 */
    for (i = 0; i < num; ++i) {
        printf("init %d-th worker of the pool\n", i);

        nWorker_t *worker = (nWorker_t *)malloc(sizeof (nWorker_t));
        if (worker == NULL) {
            perror("malloc error");
            return -2;
        }
        /* 清0工作节点 */
        memset(worker, 0, sizeof (nWorker_t));

        /* 当前工作节点绑定到当前线程池 */
        worker->poolptr = poolptr;

        /* 为每个工作节点创建1个线程, 并记录线程标识符信息 */
        ret = pthread_create(&worker->tid, NULL, worker_callback, (void *)worker);
        if (ret < 0) {
            perror("pthread_create error");
            free(worker); /* 释放本次创建的工作节点 */

            /* 释放已经存在的工作队列 */
            worker = poolptr->workerqueue.front;
            while (worker != poolptr->workerqueue.rear) {
                pthread_cancel(worker->tid); /* 尝试取消线程 */
                free(worker);
                worker = worker->next;
            }
            if (worker != NULL) {
                free(worker);
                worker = NULL;
            }

            /* 释放线程池结构 */
            free(poolptr);
            poolptr = NULL;

            return -3;
        }

        /* wokerqueue 末尾加入当前新建工作节点worker */
        if (poolptr->workerqueue.front == NULL || poolptr->workerqueue.rear == NULL ) {
            /* wokerqueue 是空队列 */
            worker->prev = worker;
            worker->next = worker;
            poolptr->workerqueue.front = worker;
        }
        else {
            /* wokerqueue 是非空队列 */
            worker->prev = poolptr->workerqueue.rear;
            worker->next = poolptr->workerqueue.front;

            poolptr->workerqueue.rear->next = worker;
            poolptr->workerqueue.front->prev = worker;
        }
        poolptr->workerqueue.rear = worker;
    }

    return 0;
}

/**
 * 向线程池加入一个用户作业
 * @param poolptr 线程池结构地址
 * @param callback 用户作业处理函数, 由用户自定义
 * @param arg 用户作业参数, 由用户定义
 * @return 加入用户作业结果
 * - < 0 加入失败
 * - 0 加入成功
 */
int fixedthreadpool_addjob(nFixedThreadPool *poolptr, jobcallback_t callback, void *arg) {
    if (poolptr == NULL) return -1;
    if (poolptr->workerqueue.front == NULL || poolptr->workerqueue.rear == NULL ) {
        fprintf(stderr, "workers is empty");
        return -1;
    }

    /* mutex 加锁 */
    pthread_mutex_lock(&poolptr->mutex);

    nJob_t *job = (nJob_t *)malloc(sizeof (nJob_t));
    if (job == NULL) {
        perror("malloc error");
        return -2;
    }
    job->next = NULL;
    job->prev = NULL;
    job->job_func = callback;
    job->user_data = arg;

    /* jobqueue 末尾加入当前作业节点job */
    if (poolptr->jobqueue.front == NULL || poolptr->jobqueue.rear == NULL) {
        /* jobqueue 是空队列*/
        job->next = job;
        job->prev = job;

        poolptr->jobqueue.front = job;
    }
    else {
        /* jobqueue 是非空队列*/
        job->prev = poolptr->jobqueue.rear;
        job->next = poolptr->jobqueue.front;

        poolptr->jobqueue.rear->next = job;
        poolptr->jobqueue.front->prev = job;
    }
    poolptr->jobqueue.rear = job;

    /* mutex 解锁, 并激活等待notempty的所有线程 */
    pthread_mutex_unlock(&poolptr->mutex);
    pthread_cond_broadcast(&poolptr->notempty);

    return 0;
}

/**
 * 销毁线程池
 * @param poolptr 线程池结构地址
 * @note 销毁线程池结构所有指针指向的区域, 包括工作队列, 作业队列, 互斥量, 条件变量等. 正在执行作业任务的线程先待其执行完毕,
 * 未执行的作业任务直接释放其资源
 */
void fixedthreadpool_destroy(nFixedThreadPool *poolptr) {
    if (poolptr == NULL) return;

    /* 置线程池关闭状态 */
    poolptr->close = 1;
    // wakeup all blocked thread of the pool
    /* 唤醒所有等待条件变量notempty的线程 */
    pthread_cond_broadcast(&poolptr->notempty);

    /* 连接工作队列线程, 线程执行完毕后释放工作队列节点 */
    nWorker_t *worker = poolptr->workerqueue.front;
    while (worker != NULL && worker != poolptr->workerqueue.rear) {
        pthread_join(worker->tid, NULL);

        free(worker);
        worker = worker->next;
    }
    if (worker != NULL) {
        /* 队列尾节点 */
        free(worker);
        worker = NULL;
    }

    /* 直接释放待执行的作业队列, 而不继续执行 */
    nJob_t *job = poolptr->jobqueue.front;
    while (job != NULL && job != poolptr->jobqueue.rear) {
        free(job);
        job = job->next;
    }
    if (job != NULL) {
        /* 队列尾节点 */
        free(job);
        job = NULL;
    }

    /* 销毁互斥量, 条件变量 */
    pthread_cond_destroy(&poolptr->notempty);
    pthread_mutex_destroy(&poolptr->mutex);

    /* 销毁线程池结构*/
    free(poolptr);
    poolptr = NULL;
}

线程池的使用testthreadpool.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "fixedthreadpool.h"

/**
 * 用户自定义任务
 * @param arg 任务处理函数参数
 */
void *mytask(void *arg) {
    int d = 0;
    if (arg != NULL) {
        d = *(int *)arg;
    }

    printf("thread %ld is working, input number is %d.\n", pthread_self(), d);
    sleep(1);
    return NULL;
}

/**
 * 客户使用线程池示例
 * @note 创建5个线程的线程池, 运行2 * 20个任务
 */
int main() {
    int i; /* for循环游标 */
    int jobid; /* 任务id */
    int tasknum = 20; /* 任务数 */
    nFixedThreadPool *pool = NULL;
    /* 创建线程池 */
    fixedthreadpool_create(&pool, 5);

    /* 创建数组num[tasknum] */
    int *num = (int *)malloc(sizeof (int) * tasknum);
    for (i = 0, jobid = 0; i < tasknum; ++i) {
        printf("add job %d to jobs\n", jobid);

        num[i] = jobid++;
        /* 向线程池加入自定义任务及参数 */
        fixedthreadpool_addjob(pool, mytask, &num[i]);
    }

    /* 创建数组anothernum[tasknum] */
    int *anothernum = (int *)malloc(sizeof (int) * tasknum);

    for (i = 0; i < tasknum; ++i) {
        printf("add job %d to jobs\n", tasknum + i);

        anothernum[i] = jobid++;
        /* 向线程池加入自定义任务及参数 */
        fixedthreadpool_addjob(pool, mytask, &anothernum[i]);
    }

    /* 等待所有任务完成 */
    sleep(10);

    /* 销毁线程池 */
    fixedthreadpool_destroy(pool);

    /* 释放数组num[], anothernum[] */
    free(num);
    free(anothernum);

    return 0;
}

测试结果:

fixedthreadpool_create, num = 5
init 0-th worker of the pool
init 1-th worker of the pool
init 2-th worker of the pool
thread 140532093028096 is waiting
init 3-th worker of the pool
thread 140532084635392 is waiting
thread 140532101420800 is waiting
init 4-th worker of the pool
thread 140532076242688 is waiting
add job 0 to jobs
thread 140532067849984 is waiting
thread 140532101420800 is waiting
add job 1 to jobs
thread 140532067849984 is waiting
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
add job 2 to jobs
thread 140532076242688 is waiting
thread 140532067849984 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532076242688 is waiting
add job 3 to jobs
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
add job 4 to jobs
thread 140532076242688 is waiting
thread 140532076242688 is waiting
add job 5 to jobs
thread 140532067849984 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532076242688 is waiting
add job 6 to jobs
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532067849984 is waiting
thread 140532076242688 is waiting
thread 140532093028096 is waiting
add job 7 to jobs
thread 140532067849984 is waiting
thread 140532084635392 is waiting
thread 140532101420800 is waiting
thread 140532076242688 is waiting
add job 8 to jobs
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
add job 9 to jobs
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
thread 140532067849984 is waiting
add job 10 to jobs
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
thread 140532076242688 is waiting
add job 11 to jobs
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
thread 140532076242688 is waiting
add job 12 to jobs
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
thread 140532076242688 is waiting
add job 13 to jobs
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
add job 14 to jobs
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532076242688 is waiting
thread 140532084635392 is waiting
add job 15 to jobs
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532076242688 is waiting
add job 16 to jobs
thread 140532076242688 is waiting
add job 17 to jobs
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
add job 18 to jobs
thread 140532076242688 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
add job 19 to jobs
thread 140532101420800 is waiting
thread 140532076242688 is waiting
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532067849984 is waiting
thread 140532076242688 is waiting
add job 20 to jobs
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532067849984 is waiting
add job 21 to jobs
thread 140532076242688 is waiting
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532067849984 is waiting
add job 22 to jobs
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
add job 23 to jobs
thread 140532084635392 is waiting
thread 140532076242688 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
add job 24 to jobs
thread 140532067849984 is waiting
thread 140532084635392 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
add job 25 to jobs
thread 140532076242688 is waiting
thread 140532067849984 is waiting
thread 140532084635392 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
add job 26 to jobs
thread 140532067849984 is waiting
thread 140532084635392 is waiting
thread 140532076242688 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
add job 27 to jobs
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532076242688 is waiting
thread 140532067849984 is waiting
thread 140532084635392 is waiting
add job 28 to jobs
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
thread 140532067849984 is waiting
add job 29 to jobs
thread 140532076242688 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532084635392 is waiting
thread 140532067849984 is waiting
add job 30 to jobs
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532076242688 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
add job 31 to jobs
thread 140532084635392 is waiting
thread 140532076242688 is waiting
thread 140532093028096 is waiting
thread 140532067849984 is waiting
add job 32 to jobs
thread 140532076242688 is waiting
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532067849984 is waiting
thread 140532084635392 is waiting
add job 33 to jobs
thread 140532076242688 is waiting
thread 140532093028096 is waiting
thread 140532101420800 is waiting
thread 140532067849984 is waiting
add job 34 to jobs
thread 140532076242688 is waiting
thread 140532084635392 is waiting
thread 140532101420800 is waiting
thread 140532093028096 is waiting
thread 140532067849984 is waiting
add job 35 to jobs
thread 140532076242688 is waiting
thread 140532101420800 is waiting
thread 140532084635392 is waiting
thread 140532093028096 is waiting
thread 140532076242688 is waiting
thread 140532067849984 is waiting
add job 36 to jobs
thread 140532101420800 is waiting
thread 140532076242688 is waiting
add job 37 to jobs
thread 140532093028096 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
add job 38 to jobs
thread 140532076242688 is waiting
thread 140532093028096 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
add job 39 to jobs
thread 140532076242688 is waiting
thread 140532093028096 is waiting
thread 140532067849984 is waiting
thread 140532101420800 is waiting
thread 140532067849984 is waiting
thread 140532093028096 is waiting
thread 140532084635392 is waiting
thread 140532076242688 is waiting
thread 140532101420800 will exit
thread 140532093028096 will exit
thread 140532067849984 will exit
thread 140532084635392 will exit
thread 140532076242688 will exit

参考

一个Linux下C线程池的实现(转) | CSDN

posted @ 2021-06-08 21:14  明明1109  阅读(466)  评论(3编辑  收藏  举报