linux C写一个项目级别的线程池

微信公众号:二进制人生
专注于嵌入式linux开发。
更新日期:2020/1/19,转载请注明出处。


愿你有所收获…
图 二进制人生公众号

快过年了,应该会停更一段时间,提前祝我的各位读者们新年快乐,2020年会有更好的发展!

最近在收集轮子或者造轮子,意在写一个嵌入式linux通用库,包含常用的各种数据结构和接口。


 

文章目录

什么是线程池

线程池是一种高并发下常用的任务后台处理模型,主要是避免为短时间处理的任务进行频繁的线程创建和销毁,以及系统中过多的线程导致过度调度,而这些都是消耗CPU资源较多的操作。

线程池创建了一个线程集合(含有n个工作线程),以及维护一个工作队列。当任务到来的时候,把任务加入到工作队列中,然后通知线程池中的线程去工作队列中取任务执行。在工作队列为空的时候,工作线程阻塞等待任务的到来。

这是一种生产者-消费者模型,由于每个线程都要访问工作队列,因此工作队列是需要进行互斥访问的,互斥访问可以通过互斥锁来实现。在添加任务到工作队列时需要通知线程池里的线程有新任务到来,这个通知的操作在linux里可以借由条件变量来实现,相当于发送一个队列有新任务到达的信号给所有线程,以唤醒他们。

由于工作队列是有容量的,不可能无上限的添加任务,在工作队列饱和时,添加任务的操作需要阻塞等待工作队列有剩余空间,即处于非满的状态。工作队列什么时候会从的状态切换到非满的状态呢?那当然是工作线程从工作队列里取出任务来执行,因此工作线程从工作队列里取出任务来执行后需要发送一个非满的信号,以唤醒那些阻塞等待队列非满、添加任务的人。

这其实就是生活中的一个现实场景。工作线程可以理解为流水线生产工人,工作队列就是流水线。工人在流水线旁边排排坐,一空闲下来就到流水线上取零件来做。

由此我们可以看到,为了实现一个线程池,我们需要以下的原材料:

  1. n个工作线程
  2. 一个工作队列
  3. 两个条件变量,一个用于通知工作线程工作队列非满,一个用于通知添加任务的人工作队列非空。
  4. 一把互斥锁
    以上是构建一个线程池最基本的数据结构。

懒得画图了,自己脑补下吧。

基本数据结构

首先我们如何来描述一个任务?或者说我们来定义一个任务实体。
一个任务在编程里的体现就是一个函数和一堆参数。

//任务实体
typedef struct {
	void *(*func)(void *);
	void *arg;                   //通常而言传入的是结构体
	void *ret;
	JOB_STATUS_E   status;       //任务状态  
} threadpool_job_t;

为了实现通用性,参数的类型为void *,同时也限定了任务函数的模板。在参数有多个的时候可以通过封装成结构体,传递结构体地址来解决。
void *ret存储任务执行的返回值,因为有时候我们需要知道任务的执行结果。为了实现这一需求,引入了第四个成员:JOB_STATUS_E status用于记录任务的状态:

//任务状态
typedef enum{
	JOB_READY       = 0x00,        //待执行
	JOB_DONE        = 0x01,        //表示任务是否已经完成
	JOB_FULLY_DONE  = 0x02,        //表示任务返回值是否已经回收
}JOB_STATUS_E;

经过前面的分析,一个线程池应该包含以下数据结构:

//线程池
typedef struct {
	volatile int    exit;
	int             nthreads;              //工作线程数
	pthread_t       *thread_handle;        //存储线程id
	void            (*init_func)(void *);
	void            *init_arg;
	CirQueue_T      jobQueue;             //任务循环队列

	pthread_mutex_t mutex;
	pthread_cond_t  cv_fill;         /* event signaling that the list became fuller */
	pthread_cond_t  cv_empty;        /* event signaling that the list became emptier */
	pthread_cond_t  job_done;        /* event signaling that job done */
}threadpool_t;

CirQueue_T是我们前面实现的一个循环队列,这里我们直接拿来使用了,关于循环队列可以参看前面的文章。
有两个特殊的参数:

void            (*init_func)(void *);
void            *init_arg;

分别是线程初始化函数和参数,由用户自定义传入,它会在线程起来的时候调用,比如可以执行设置线程名字,线程优先级,亲核性等操作,无特别需求时指定为NULL即可。

可以看到我们定义了三个条件变量,说好的不是两个吗?多出来的一个叫pthread_cond_t job_done,和任务状态回收有关,在任务完成时会发送任务完成信号,以让回收任务状态的人来进行回收。

自认为代码写的很清晰,所以我们还是看代码吧。关于队列实现的代码没有贴出,应该不影响阅读,你把它当做是简单的入队和出队操作即可。本代码至少有5个以上可以思考的点,请细细体味。

项目级封装

threadpool.h头文件

#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_

#ifndef _THREAD_POOL_C_
#define FUN_THREAD extern
#else
#define FUN_THREAD
#endif

#include "circular_queue.h"

//任务状态
typedef enum{
	JOB_READY       = 0x00,        //待执行
	JOB_DONE        = 0x01,        //表示任务是否已经完成
	JOB_FULLY_DONE  = 0x02,        //表示任务返回值是否已经回收
}JOB_STATUS_E;

//任务实体
typedef struct {
	void *(*func)(void *);
	void *arg;           //通常而言传入的是结构体
	void *ret;
	JOB_STATUS_E   status;       //任务状态  
} threadpool_job_t;

//线程池
typedef struct {
	volatile int    exit;
	int             nthreads;              //工作线程数
	pthread_t       *thread_handle;        //存储线程id
	void            (*init_func)(void *);
	void            *init_arg;
	CirQueue_T      jobQueue;             //任务循环队列

	pthread_mutex_t mutex;
	pthread_cond_t  cv_fill;         /* event signaling that the list became fuller */
	pthread_cond_t  cv_empty;        /* event signaling that the list became emptier */
	pthread_cond_t  job_done;        /* event signaling that job done */
}threadpool_t;

FUN_THREAD int threadpool_init( threadpool_t **p_pool, int nthreads, void (*init_func)(void *), void *init_arg );

FUN_THREAD void threadpool_destroy( threadpool_t *pool );

FUN_THREAD void threadpool_run( threadpool_t *pool, void *(*func)(void *), void *arg );

FUN_THREAD void *threadpool_wait( threadpool_t *pool, void *arg );

#endif

threadpool.c文件

#ifndef _THREAD_POOL_C_
#define _THREAD_POOL_C_

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "threadpool.h"

static void job_queue_push( threadpool_t *p_pool, void *job )
{
	pthread_mutex_lock( &p_pool->mutex );

	while ( isFullCirQueue(&p_pool->jobQueue) )//任务数组已经满,等待任务处理线程处理完一个任务
	{
		pthread_cond_wait( &p_pool->cv_empty, &p_pool->mutex );
	}
	enCirQueue(&p_pool->jobQueue, job);
	
	pthread_mutex_unlock( &p_pool->mutex );
	pthread_cond_broadcast( &p_pool->cv_fill );//通知别人有数据可用
}

static void job_queue_destroy( CirQueue_T *jobQueue )
{
	DestroyCirQueue(jobQueue);
}

//等待arg标识的任务执行完毕,且回收其返回值
void *threadpool_wait( threadpool_t *pool, void *arg )
{
	pthread_mutex_lock( &pool->mutex );
	threadpool_job_t *job;
	int i;
	while ( 1 )
	{
		cir_queue_for_each_all((&pool->jobQueue), i, job){//是否可以不需要全部遍历
			//printf("i=%d,arg=%0x,job->arg=%0x,job->status=%d\n",i,arg,job->arg,job->status);
			if ( job->arg == arg && job->status == JOB_DONE)//找到了并且任务已经完成
			{	
				job->status = JOB_FULLY_DONE;//表示状态已经回收,任务彻底完成
				pthread_cond_broadcast( &pool->cv_empty );//通知别人有空间可用
				pthread_mutex_unlock( &pool->mutex );
				return job->ret;       //返回执行结果
			}
		}
		pthread_cond_wait( &pool->job_done, &pool->mutex );//有任务执行完了,再次检测
	}
}

//任务处理线程实体
void *threadpool_thread(void *arg )
{
	if (arg == NULL)
	{
		return NULL;
	}
	threadpool_t *pool = (threadpool_t *)arg;

	if ( pool->init_func )
	{
		pool->init_func( pool->init_arg );
	}

	threadpool_job_t *pjob;
	int ret = 0;
	while ( !pool->exit )
	{
		while(!isEmptyCirQueue(&pool->jobQueue))//加多一层判断,保证所有任务处理完才退出
		{
			pthread_mutex_lock( &pool->mutex );
			while ( !pool->exit && isEmptyCirQueue(&pool->jobQueue) )//pool->exit不能少,注意思考
			{
				pthread_cond_wait( &pool->cv_fill, &pool->mutex );//等待有任务到达
			}
			pjob = deCirQueueIn(&pool->jobQueue);
			if ( pjob == NULL )//在线程退出时可能会出现这种情况,可以思考下
			{			
				pthread_mutex_unlock( &pool->mutex );
				break;
			}
			pthread_cond_broadcast( &pool->cv_empty );//通知别人有空间可用

			pthread_mutex_unlock( &pool->mutex );
		
			pjob->ret = pjob->func( pjob->arg );      //执行任务
			pjob->status = JOB_DONE;                  //置位任务执行完毕
			pthread_cond_broadcast( &pool->job_done );//通知回收任务状态的人任务完成
		}
	}
	//printf("thread exit\n");
	return NULL;
}

//线程池初始化:
//创建nthreads个线程
//参数init_func和init_arg是用户想在线程里调用的初始化函数
//比如可以执行设置线程名字,线程优先级,亲核性等操作,无特别需求时传入NULL即可
int threadpool_init( threadpool_t **p_pool, int nthreads, void (*init_func)(void *), void *init_arg )
{
	if ( nthreads <= 0 )
	{
		return -1;
	}

	threadpool_t *pool;
	pool = (threadpool_t *)malloc( sizeof(threadpool_t) );
	if (pool == NULL)
	{
		return -1;
	}
	*p_pool = pool;

	pool->init_func = init_func;
	pool->init_arg = init_arg;
	pool->nthreads = nthreads;

	pool->thread_handle = (pthread_t *)malloc( pool->nthreads * sizeof(pthread_t) );
	if (pool->thread_handle == NULL)
	{
		goto fail1;
	}
	memset(pool->thread_handle,0,sizeof(pool->nthreads * sizeof(pthread_t) ));

	if ( pthread_mutex_init( &pool->mutex, NULL ) ||
	     pthread_cond_init( &pool->cv_fill, NULL ) ||
	     pthread_cond_init( &pool->cv_empty, NULL ) ||
	     pthread_cond_init( &pool->job_done, NULL ))
	{
		goto fail2;
	}

	InitCirQueue(nthreads, &pool->jobQueue, sizeof(threadpool_job_t));

	int i;
	for ( i = 0; i < pool->nthreads; i++ )
	{
		if ( pthread_create( pool->thread_handle +i , NULL, (void*)threadpool_thread, (void *)pool ) < 0 )
		{
			goto fail3;
		}
		//printf("create[%d]=%lu\n",i,pool->thread_handle[i]);
	}

	return 0;

fail3:
	DestroyCirQueue(&pool->jobQueue);
	//等待线程退出
	pool->exit = 1;
	pthread_cond_broadcast( &pool->cv_fill );       //可能会有工作线程处于阻塞状态,告知它退出
	pthread_mutex_unlock( &pool->mutex );           //解锁也是必要的
	for ( int i = 0; i < pool->nthreads; i++ )      //等待所有线程运行结束
	{
		if(pool->thread_handle[i])
			pthread_join( pool->thread_handle[i], NULL );
	}

fail2:
	free(pool->thread_handle );
	pthread_mutex_destroy( &pool->mutex );
	pthread_cond_destroy( &pool->cv_fill );
	pthread_cond_destroy( &pool->cv_empty );
	
fail1:
	free(pool);

	//printf("init threadpool failed\n");
	return -1;
}

void threadpool_destroy( threadpool_t *pool )
{
	pool->exit = 1;                                 //工作线程以该变量作为while循环标志,所以会导致工作线程结束循环
	pthread_cond_broadcast( &pool->cv_fill );       //可能会有工作线程处于阻塞状态,告知它退出
	pthread_mutex_unlock( &pool->mutex );           //解锁也是必要的

	for ( int i = 0; i < pool->nthreads; i++ )      //等待所有线程运行结束
	{
		//printf("join %lu\n", pool->thread_handle[i]);
		pthread_join( pool->thread_handle[i], NULL );
	}

	pthread_mutex_destroy( &pool->mutex );
	pthread_cond_destroy( &pool->cv_fill );
	pthread_cond_destroy( &pool->cv_empty );

	job_queue_destroy( &pool->jobQueue );             //销毁任务队列
 
	free( pool->thread_handle );
	free( pool );
}

//对外调用的任务执行函数,可能会阻塞
void threadpool_run( threadpool_t *pool, void *(*func)(void *), void *arg )
{
	threadpool_job_t job;
	job.func = func;
	job.arg = arg;
	job.status = JOB_READY;
	job_queue_push( pool , &job);
}

//测试用例
struct data {
	int a;
};

void *fun(void *arg)
{
	struct data *ptr = (struct data*)arg;

	printf("%d\n", ptr->a);
	sleep(3);//加点延时
	return (void*)ptr->a;
}

int main()
{
	threadpool_t *p_pool;

	struct data d[10];

	int count = 0;

	threadpool_init( &p_pool, 5, NULL, NULL );//指定工作线程个数为5
	while (count < 10)
	{
		d[count].a = count;
		threadpool_run( p_pool, fun, (void *)&d[count]);
		//threadpool_wait( p_pool, &d);//等待上面的任务执行完毕,可以不加这个函数
		count++;
	}
	threadpool_destroy( p_pool );

	return 0;
}
#endif

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值