线程池简单实现(基于Linux C)

​ 线程池常用于高性能服务器中。为了减小线程创建、销毁时的开销,引入了池式结构。比如:内存池、数据库连接池等等。

学习线程池也只要是因为最近在学网络编程的东西。

环境

wsl2(ubunt1804)、gcc7.5.0、cmake3.0.0

cmakelists.txt添加一下两句

find_package(Threads)
target_link_libraries(thread_pool ${CMAKE_THREAD_LIBS_INIT})

线程池结构

在这里插入图片描述

从上图可以看到线程池主要包括任务队列、线程数组、管理者线程。为了简单起见,没有加入管理者线程。

调用顺序

//TODO

线程池数据结构设计

线程池结构定义

主要包括线程池的一些属性。线程池状态信息、任务队列信息、互斥锁

struct threadpool_t{
    pthread_mutex_t lock;     //用于内部工作的互斥锁
    pthread_cond_t notify;    //线程间通知的条件变量
    pthread_t *threads;       //线程数组
    threadpool_task_t *queue; //存储任务的数组,即任务队列
    int thread_count;         //线程数量
    int queue_size;           //任务队列大小
    int head;                 //任务队列中首个任务位置(注:任务队列中所有任务都是未开始运行的)
    int tail;                 //任务队列中最后一个任务的下一个位置(注:队列以数组存储,head 和 tail 指示队列位置)
    int count;                //任务队列里的任务数量,即等待运行的任务数
    int shutdown;             //表示线程池是否关闭
    int started;              //开始的线程数   
};

任务队列结构定义

可以放置多种不同任务函数的函数指针,参数为void*型

typedef struct
{
    void (*function)(void *);	//函数指针,参数为void*。任意线程函数
    void *argument;				//线程函数参数
} threadpool_task_t;

其他一些枚举类型

错误码

    typedef enum
    {
        threadpool_invalid = -1,
        threadpool_lock_failure = -2,
        threadpool_queue_full = -3,
        threadpool_shutdown = -4,
        threadpool_thread_failure = -5
    } threadpool_terror_t;

线程池关闭方式

typedef enum {
    immediate_shutdown = 1, //自动关闭
    graceful_shutdown  = 2	//立即关闭
} threadpool_shutdown_t;

线程池销毁的枚举

typedef enum
{
    threadpool_graceful = 1
} threadpool_destroy_flags_t;

线程池API设计

线程池对外开放了三个API:

/**
 * @function 			threadpool_create
 * @brief	 			创建线程池
 * @param thread_count 	工作线程数量
 * @param thread_queue 	任务队列大小
 * @param flags 	   	未使用的参数
 * @return 				返回一个新创建的线程池或者NULL
**/
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags);

/**
 * @function 			threadpool_add
 * @brief	 			添加新任务到线程池队列中
 * @param pool 			线程池指针
 * @param routine 		函数指针
 * @param arg       	传入函数的参数
 * @param flags 		未使用的参数
 * @return 				成功返回0,失败返回错误码
**/
int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg, int flags);

/**
 * @function 			threadpool_destroy
 * @brief	 			停止并且摧毁线程池
 * @param pool 			线程池指针
 * @param flags 		指定关闭方式
**/
int threadpool_destroy(threadpool_t *pool, int flags);

此外还有一个工作线程未对外开放:

static void *threadpool_thread(void *threadpool);

为了避免内存越界:

int threadpool_free(threadpool_t *pool);

创建线程池

创建和运行线程池

threadpool_t *threadpool_create(int thread_count, int queue_size, int flags)
{
    //对传入参数进行判断,MAX_THREADS、MAX_QUEUE为宏定义,自行定义
    if (thread_count <= 0 || thread_count > MAX_THREADS || 
        queue_size <= 0 || queue_size > MAX_QUEUE) 
    {
        return NULL;
    }
	
    //声明线程池变量
    threadpool_t *pool;
	
    //动态申请线程池空间,失败goto err
    if ((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL)
    {
        goto err;
    }
	
    //对线程池结构体参数进行初始化
    pool->thread_count = 0;
    pool->queue_size = queue_size;
    pool->head = pool->tail = pool->count = 0;
    pool->shutdown = pool->started = 0;
	
    //线程数组动态申请
    pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count);
    //任务队列动态申请
    pool->queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_size);
	
    //初始化互斥锁、条件变量并判断线程数组、任务队列等是否申请(初始化成功)
    if ((pthread_mutex_init(&pool->lock, NULL) != 0) ||
        (pthread_cond_init(&pool->notify, NULL) != 0) ||
        (pool->threads == NULL) || (pool->queue == NULL))
    {
        goto err;
    }
	
    //创建指定的线程并开始运行
    for (size_t i = 0; i < thread_count; i++)
    {
        if (pthread_create(&pool->threads[i], NULL, threadpool_thread, (void *)pool) != 0)
        {
            threadpool_destroy(pool, 0);
            return NULL;
        }
        pool->thread_count++;	//每成功创建一个线程,工作线程数量++
        pool->started++;		//每运行一个线程,运行中线程数量++
    }
    return pool;

err:
    if (pool)
    {
        threadpool_free(pool);
    }
    return NULL;
}

添加任务到线程池

int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags)
{
    int err = 0;
    int next;
	//参数判断
    if (pool == NULL || function == NULL)
    {
        return threadpool_invalid;
    }
	//对资源进行上锁。若上锁失败,会阻塞。
    if (pthread_mutex_lock(&(pool->lock)) != 0)
    {
        return threadpool_lock_failure;
    }
	
    //计算下一个可以存储任务的位置
    next = pool->tail + 1;
    //判断next到没到任务队列的上限,没有就next = next
    next = (next == pool->queue_size) ? 0 : next;
	
    //此处使用do {...}while(0)结构是为了保证至少运行一次。如果有异常,直接break掉,不运行后边的语句
    do
    {	
        //判断线程池中任务数量是否小于任务队列上限
        if (pool->count == pool->queue_size)
        {
            err = threadpool_queue_full;
            break;
        }
		
        //添加任务到任务队列。queue实际大小是0~255,而next是从1开始,1~256
        pool->queue[pool->tail].function = function;
        pool->queue[pool->tail].argument = argument;
		
        //这句就相当于pool->tail++
        pool->tail = next;
        //任务数量++
        pool->count += 1;
		
        //条件变量。任务添加到队列以后,发出信号通知空闲线程取任务。
        if (pthread_cond_signal(&(pool->notify)) != 0)
        {
            err = threadpool_lock_failure;
            break;
        }
    } while (0);
	
    //释放资源
    if (pthread_mutex_unlock(&(pool->lock)) != 0)
    {
        err = threadpool_lock_failure;
    }

    return err;
}

线程池销毁

线程池的销毁比较简单。

int threadpool_destroy(threadpool_t *pool, int flags)
{
    int i, err = 0;

    if (pool == NULL)
        return threadpool_invalid;
    //对资源进行上锁(取得互斥锁资源)
    if (pthread_mutex_lock(&(pool->lock)) != 0)
        return threadpool_lock_failure;
    
	//此处使用do {...}while(0)结构是为了保证至少运行一次。如果有异常,直接break掉,不运行后边的语句
    do
    {
        //判断是否已经关闭了线程池
        if (pool->shutdown)
        {
            err = threadpool_shutdown;
            break;
        }
        
        //指定线程池关闭方式;graceful_shutdown为自动关闭,即等待所有线程完成任务后退出
        //immediate_shutdown,立即退出,不等待
        pool->shutdown = (flags & threadpool_graceful) ? graceful_shutdown : immediate_shutdown;
		
        //唤醒所有因条件变脸阻塞的线程,并释放互斥锁
        if ((pthread_cond_broadcast(&(pool->notify)) != 0) || (pthread_mutex_unlock(&(pool->lock)) != 0))
        {
            err = threadpool_lock_failure;
            break;
        }
		
        //等待子线程退出、并回收线程资源
        for (size_t i = 0; i < pool->thread_count; i++)
        {
            if (pthread_join(pool->threads[i], NULL) != 0)
            {
                err = threadpool_thread_failure;
            }
        }
    } while (0);

    if (!err)
    {
        threadpool_free(pool);
    }

    return err;
}

释放互斥锁、条件变量等资源

int threadpool_free(threadpool_t *pool)
{
    //判断参数是否合法
    if (pool == NULL || pool->started > 0)
    {
        return -1;
    }
	
    //释放互斥锁、条件变量、malloc出的条件队列、线程数组等
    if (pool->threads)
    {
        free(pool->threads);
        free(pool->queue);
		
        //以防万一,加锁后再destroy。不写应该没有啥问题
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_cond_destroy(&(pool->notify));
    }
    free(pool);
    return 0;
}

工作线程

static void *threadpool_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool;
    threadpool_task_t task;

    for (;;)
    {
        /* 取得互斥资源 */
        pthread_mutex_lock(&(pool->lock));
		//使用while是为了在唤醒时重新检查条件
        while ((pool->count == 0) && (!pool->shutdown))
        {
            //任务队列为空,且线程池没有关闭时,阻塞在这里。
            pthread_cond_wait(&(pool->notify), &(pool->lock));
        }
		
        //判断线程池关闭方式,immediate_shutdown时,立即break,并退出
        //graceful_shutdown时,要等待线程池中任务被全部取走,即count == 0时
        if ((pool->shutdown == immediate_shutdown) || ((pool->shutdown == graceful_shutdown) && (pool->count == 0)))
        {
            break;
        }
		
        //取得任务队列第一个任务
        task.function = pool->queue[pool->head].function;
        task.argument = pool->queue[pool->head].argument;
		
        //更新head、cout
        pool->head += 1;
        pool->head = (pool->head == pool->queue_size) ? 0 : pool->head;
        pool->count -= 1;
		
        //释放互斥资源
        pthread_mutex_unlock(&(pool->lock));
		
        //运行任务
        (*(task.function))(task.argument);
    }
	
    //线程将结束、更新运行线程数
    pool->started--;

    pthread_mutex_unlock(&(pool->lock));
    pthread_exit(NULL);
    return NULL;
}

测试用例

#include "threadpool.h"
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <unistd.h>

#define THREAD 32
#define QUEUE 256

int tasks = 0, done = 0;
pthread_mutex_t lock;

void dummy_task(void *arg)
{
    usleep(10000);
    pthread_mutex_lock(&lock);
    /* 记录成功完成的任务数 */
    done++;

    pthread_mutex_unlock(&lock);
}

int main(int argc, char **argv)
{
    threadpool_t *pool;
	
    /* 初始化互斥锁 */
    pthread_mutex_init(&lock, NULL);

    /* 断言线程池创建成功 */
    assert((pool = threadpool_create(THREAD, QUEUE, 0)) != NULL);
    fprintf(stderr, "Pool started with %d threads and queue size of %d\n", THREAD, QUEUE);

    /* 只要任务队列还没满,就一直添加 */
    while (threadpool_add(pool, &dummy_task, NULL, 0) == 0)
    {
        pthread_mutex_lock(&lock);
        tasks++;
        pthread_mutex_unlock(&lock);
    }

    fprintf(stderr, "added %d tasks\n", tasks);
    
    /* 不断检查任务数是否完成一半以上,没有则继续休眠 */
    while ((tasks / 2) > done)
    {
        usleep(10000);
    }
    
	/* 这时候销毁线程池,0 代表 immediate_shutdown 1 代表 graceful_shutdown*/
    assert(threadpool_destroy(pool, 1) == 0);
    fprintf(stderr, "did %d tasks\n", done);

    return 0;
}

pthread库一些函数

创建线程

/**
 * @function 				pthread_create
 * @brief	 				创建线程
 * @param thread	 		线程标识符地址
 * @param attr		 		线程属性结构体地址
 * @param start_routine 	线程函数的入口地址
 * @param arg				传给线程函数的参数
 * @return 					成功 返回0;失败 返回 非0 
**/
int pthread_create(pthread_t *thread,const pthread_attr_t *attr, void *(*start_routine)(void *),void *arg);

初始化互斥锁

int pthread_mutex_init(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr)

初始化条件变量

int pthread_cond_init(pthread_cond_t *__restrict__ __cond, const pthread_condattr_t *__restrict__ __cond_attr)

//TODO

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值