微信公众号:二进制人生
专注于嵌入式linux开发。
更新日期:2020/1/19,转载请注明出处。
愿你有所收获…
快过年了,应该会停更一段时间,提前祝我的各位读者们新年快乐,2020年会有更好的发展!
最近在收集轮子或者造轮子,意在写一个嵌入式linux通用库,包含常用的各种数据结构和接口。
文章目录
什么是线程池
线程池是一种高并发下常用的任务后台处理模型,主要是避免为短时间处理的任务进行频繁的线程创建和销毁,以及系统中过多的线程导致过度调度,而这些都是消耗CPU资源较多的操作。
线程池创建了一个线程集合(含有n个工作线程),以及维护一个工作队列。当任务到来的时候,把任务加入到工作队列中,然后通知线程池中的线程去工作队列中取任务执行。在工作队列为空的时候,工作线程阻塞等待任务的到来。
这是一种生产者-消费者模型,由于每个线程都要访问工作队列,因此工作队列是需要进行互斥访问的,互斥访问可以通过互斥锁来实现。在添加任务到工作队列时需要通知线程池里的线程有新任务到来,这个通知的操作在linux里可以借由条件变量来实现,相当于发送一个队列有新任务到达的信号给所有线程,以唤醒他们。
由于工作队列是有容量的,不可能无上限的添加任务,在工作队列饱和时,添加任务的操作需要阻塞等待工作队列有剩余空间,即处于
非满
的状态。工作队列什么时候会从满
的状态切换到非满
的状态呢?那当然是工作线程从工作队列里取出任务来执行,因此工作线程从工作队列里取出任务来执行后需要发送一个非满
的信号,以唤醒那些阻塞等待队列非满、添加任务的人。这其实就是生活中的一个现实场景。工作线程可以理解为流水线生产工人,工作队列就是流水线。工人在流水线旁边排排坐,一空闲下来就到流水线上取零件来做。
由此我们可以看到,为了实现一个线程池,我们需要以下的原材料:
- n个工作线程
- 一个工作队列
- 两个条件变量,一个用于通知工作线程工作队列非满,一个用于通知添加任务的人工作队列非空。
- 一把互斥锁
以上是构建一个线程池最基本的数据结构。懒得画图了,自己脑补下吧。
基本数据结构
首先我们如何来描述一个任务?或者说我们来定义一个任务实体。
一个任务在编程里的体现就是一个函数和一堆参数。//任务实体 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