简介:为了减少频繁创建和销毁线程带来的开销,提高程序的效率,可使用线程池处理并发失误,实时监控并控制线程数量,并使得任务先进先出有序处理。以下附上linux上C语言编写的线程池,并将测试数据打印到终端。
环境:redhat
设计简要:1、线程池分为管理者线程一个和工作线程若干。
2、管理者线程负责循环读取task链表头,若存在则判断是否有空闲线程且是否需要创建新的线程还是等待,有空闲线程之后发送cond给其中一个wait中的工作线程,并等待wait工作线程返回getTaskCond。
3、工作线程创建后先分离属性,再等到cond,获取cond后并拿到任务节点地址,是任务头向后便宜并更改当前线程池全局数据结构体中的正在工作线程数量后,再向管理者线程getTaskCond。自身在处理任务节点中的函数,处理完之后释放节点内存空间。
4、管理者线程获得getTaskCond后进入下一个循环。
5、管理者线程每隔一段时间需要根据一定的规则清理一次线程池中的空闲线程。
头文件:thread_pool.h
#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
/*任务链表节点*/
typedef struct task_node
{
int (*call)(void *); //函数指针
void *arg; //参数指针
struct task_node *next;
}task_node_t;
/*线程池全局数据结构体*/
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t cond; //工作线程等待条件
pthread_cond_t condGetTask; //管理者线程等待条件
pthread_t managerTid; //管理者线程id
task_node_t *pTaskHead; //任务链表头
task_node_t *pTaskTail; //任务链表尾
int iMaxThreadCount; //最大线程数量
int iMinThreadCount; //最小线程数量
int iWorkThreadCount; //当前正在工作的线程数量
int iAliveThreadCount; //当前所有存活线程数量(不包括管理者线程)
int iShutdownFlag; //关闭标志
}thread_info_t;
//参数结构体,将所有待执行函数可能用到的参数都放入此结构体,对用处理函数用不到的参数赋0即可
//这里测试函数fun_demo1使用到id和name,fun_demo2使用到age和sex
typedef struct
{
unsigned int iId;
char aName[30];
unsigned int iAge;
char aSex[10];
char aData[1024];
}arg_struct_t;
//创建并初始化线程池
int create_thread_pool(int min_count , int max_count);
//添加任务节点到任务链表
int add_task(int (*call)(void *arg) , void *arg);
//工作线程
void *thread_handle(void *arg);
//管理者线程
void *thread_manager(void *arg);
//销毁线程池
int destroy_thread_pool();
//线程任务函数,自我销毁
int fun_thread_kill_self(void *arg);
//func demos
int fun_demo1_printIdName(void *arg);
int fun_demo2_printAgeSex(void *arg);
//管理者线程信号处理函数,清理多余空闲线程
void sig_clean(int signum);
#endif
源文件:thread_pool.c
注:主线程通过add_task函数添加工作任务节点,管理者线程循环遍历任务头结点等待或创建空闲线程再通知空闲线程并等待响应条件,空闲线程获取任务节点后更新数据再响应管理者线程。只有管理者线程一个线程使用signal和alarm组合,并且主线程需要pthread_sigmask屏蔽闹钟信号,若多个线程使用alarm只有一个线程起作用。
1、create_thread_pool函数创建最小线程数量的工作线程,并创建一个管理者线程记下tid到全局结构体中。
2、add_task更新任务链表数据。
3、thread_handle工作线程函数,等待cond获取任务节点更新正在工作线程数量后再返回getTaskCond,处理完函数后再更新工作线程数量。
4、thread_manager管理者线程函数,循环遍历任务头结点等待或创建空闲线程再通知空闲线程并等待响应条件进入下一次循环,并且定时监控线程数量,清理多余线程。
5、sig_clean信号处理函数,管理者线程循环alarm定时销毁多余线程。
6、destroy_thread_pool销毁线程池函数,修改全局数据结构体中的shutdown标志,并广播所有线程直到存活线程数量为0,再销毁管理者线程,再清理一些其他数据。
7、fun_thread_kill_self工作线程分离属性,shutdown标志为真表示线程池销毁,pthread_exit自行退出。
8、fun_demo1_printIdName测试函数,3秒打印一次id和姓名。
9、fun_demo2_printAgeSex测试函数,2秒打印一次age和性别。(3秒和2秒打印不同数据有利观察线程国内工作状态)
#include "thread_pool.h"
#define _CLEAN_TIME_ 30
#define _RETRY_CREATE_TIMES_ 3
static thread_info_t sgThreadInfo;
int create_thread_pool(int min_count , int max_count)
{
int i = 0;
int ilRet = 0;
int ilRetry = 0;
memset(&sgThreadInfo , 0 , sizeof(thread_info_t));
if( pthread_mutex_init(&(sgThreadInfo.mutex) , NULL) != 0 )
{
puts("pthread_mutex_init() failed!");
return -1;
}
if(pthread_cond_init(&(sgThreadInfo.cond), NULL) != 0)
{
puts("pthread_cond_init() cond failed!");
return -1;
}
if(pthread_cond_init(&(sgThreadInfo.condGetTask) , NULL) != 0)
{
puts("pthread_cond_init() condGetTask failed!");
return -1;
}
sgThreadInfo.iMinThreadCount = min_count;
sgThreadInfo.iMaxThreadCount = max_count;
sgThreadInfo.iShutdownFlag = 0;
//创建min_count数量的线程
for(i = 0 ; i<min_count ; )
{
pthread_t tid;
ilRet = pthread_create(&tid , NULL , thread_handle , NULL);
if(ilRet != 0)
{
if(ilRetry == _RETRY_CREATE_TIMES_)
{
printf("[ERROR] pthread_create() retry [%d] times failed! Exit...\n" , _RETRY_CREATE_TIMES_);
return -1;
}
puts("[ERROR] pthread_create() failed! Retry ..");
ilRetry++;
continue;
}
sgThreadInfo.iAliveThreadCount++;
i++;
}
printf("Thread pool init completed! Alive_thread_count is [%d]..\n" , sgThreadInfo.iAliveThreadCount);
ilRet = pthread_create(&(sgThreadInfo.managerTid) , NULL , thread_manager , NULL);
if(ilRet != 0)
{
puts("[ERROR] Create thread manager failed! Exit...");
return -1;
}
printf("Create thread manager successful! Manager's tid is [%lu]\n" , sgThreadInfo.managerTid);
return 0;
}
int add_task(int (*call)(void *arg) , void *arg)
{
//上锁添加任务节点
pthread_mutex_lock(&(sgThreadInfo.mutex));
if(sgThreadInfo.pTaskTail == NULL)
{
sgThreadInfo.pTaskHead = (task_node_t *)malloc(sizeof(task_node_t));
memset(sgThreadInfo.pTaskHead , 0 , sizeof(task_node_t));
sgThreadInfo.pTaskTail = sgThreadInfo.pTaskHead;
sgThreadInfo.pTaskHead->call = call;
sgThreadInfo.pTaskHead->arg = arg;
}
else
{
sgThreadInfo.pTaskTail->next = (task_node_t *)malloc(sizeof(task_node_t));
memset(sgThreadInfo.pTaskTail->next , 0 , sizeof(task_node_t));
sgThreadInfo.pTaskTail = sgThreadInfo.pTaskTail->next;
sgThreadInfo.pTaskTail->call = call;
sgThreadInfo.pTaskTail->arg = arg;
}
pthread_mutex_unlock(&(sgThreadInfo.mutex));
return 0;
}
void *thread_handle(void *arg)
{
int (*call)(void *);
void *argTmp;
task_node_t *plTmp;
int ilRet = 0;
//设置线程分离,不必在主进程join等待线程回收
pthread_detach(pthread_self());
//线程无限循环等待新的任务
while(1)
{
pthread_mutex_lock(&(sgThreadInfo.mutex));
//无任务、非关闭状态下则等待signal信号
if(sgThreadInfo.pTaskHead == NULL && \
sgThreadInfo.iShutdownFlag == 0)
{
//等待signal通知,等待期间pthread_cond_wait函数会自动释放锁供其他线程使用,收到cond后再上锁
pthread_cond_wait(&(sgThreadInfo.cond) , &(sgThreadInfo.mutex));
}
//优先级 :shutdown > task ; 关闭时任务队列中的任务直接抛弃
if(sgThreadInfo.iShutdownFlag == 1)
{
//减少存活线程数量
sgThreadInfo.iAliveThreadCount--;
printf("Shutdown , tid[%lu] exit! Alive thread count [%d]\n" , pthread_self() , sgThreadInfo.iAliveThreadCount);
pthread_mutex_unlock(&(sgThreadInfo.mutex));
pthread_exit(NULL);
}
else if(sgThreadInfo.pTaskHead)
{
//增加工作线程数量
sgThreadInfo.iWorkThreadCount++;
printf("New task! Tid[%lu], Alive count[%d] ~ Work count[%d]\n" , pthread_self() , \
sgThreadInfo.iAliveThreadCount , \
sgThreadInfo.iWorkThreadCount);
//获取线程中的函数及参数指针,并使全局变量的head向后偏移
call = sgThreadInfo.pTaskHead->call;
argTmp = sgThreadInfo.pTaskHead->arg;
plTmp = sgThreadInfo.pTaskHead;
sgThreadInfo.pTaskHead = sgThreadInfo.pTaskHead->next;
if(sgThreadInfo.pTaskHead == NULL)
{
sgThreadInfo.pTaskTail = NULL;
}
//修改任务链表头结点和工作线程数量后,发送condGetTask信号让manager线程结束等待分配下一个任务
pthread_cond_signal(&(sgThreadInfo.condGetTask));
//解锁去执行任务
pthread_mutex_unlock(&(sgThreadInfo.mutex));
ilRet = (*call)(argTmp);
if(ilRet != 0)
{
printf("[ERROR] 执行函数指针失败!\n");
}
//执行完成,释放节点
free(plTmp);
//释放参数结构体指针指向的空间
free(argTmp);
//任务执行完成,上锁去修改工作线程数量的值
pthread_mutex_lock(&(sgThreadInfo.mutex));
sgThreadInfo.iWorkThreadCount--;
printf("Tid[%lu] exec func completed! Alive count[%d] ~ Work count[%d]\n" , pthread_self() , \
sgThreadInfo.iAliveThreadCount , \
sgThreadInfo.iWorkThreadCount);
//全部完成,解锁
pthread_mutex_unlock(&(sgThreadInfo.mutex));
//等待获取下一个任务
continue;
}
else
{
printf("Invalid condition! Continue.\n");
continue;
}
}
return NULL;
}
int fun_thread_kill_self(void *arg)
{
pthread_mutex_lock(&(sgThreadInfo.mutex));
sgThreadInfo.iAliveThreadCount--;
sgThreadInfo.iWorkThreadCount--;
printf("Clean self , tid[%lu] exit! Alive count is [%d]\n" , pthread_self() , sgThreadInfo.iAliveThreadCount);
pthread_mutex_unlock(&(sgThreadInfo.mutex));
pthread_exit(NULL);
return 0;
}
int fun_demo1_printIdName(void *arg)
{
int i = 0;
for(i = 0 ; i < 8 ; i++)
{
printf("Tid[%lu] ------id[%d]~name[%s]-------\n" , pthread_self() , ((arg_struct_t *)arg)->iId , ((arg_struct_t *)arg)->aName);
sleep(3);
}
return 0;
}
int fun_demo2_printAgeSex(void *arg)
{
int i = 0;
for(i = 0 ; i < 6 ; i++)
{
printf("Tid[%lu] >>>>>>age[%d]~sex[%s]<<<<<<<\n" , pthread_self() ,((arg_struct_t *)arg)->iAge , ((arg_struct_t *)arg)->aSex);
sleep(2);
}
return 0;
}
//线程管理者,循环定时清理多余线程
void *thread_manager(void *arg)
{
//根据规则控制存活线程数量
int ilIsHaveIdleThreadFlag = 0;
int ilRet = 0;
signal(SIGALRM , sig_clean);
alarm(_CLEAN_TIME_);
while(1)
{
//取消点
pthread_testcancel();
//上锁判断是否有空闲线程
pthread_mutex_lock(&(sgThreadInfo.mutex));
//每次循环都检查任务头节点并是否存在,若存在则判断是否有空闲线程执行任务或创建新的线程
if(sgThreadInfo.pTaskHead)
{
//符合条件则创建新线程,判断存活线程数量是否等于工作线程,根据情况判定是否需要新增线程执行任务
if(sgThreadInfo.iAliveThreadCount == sgThreadInfo.iWorkThreadCount && \
sgThreadInfo.iAliveThreadCount < sgThreadInfo.iMaxThreadCount)
{
pthread_t tid;
ilRet = pthread_create(&tid , NULL , thread_handle , NULL);
if(ilRet != 0)
{
puts("pthread_create() failed! Retry...");
pthread_mutex_unlock(&(sgThreadInfo.mutex));
continue;
}
printf("Thread count is not full , pthread_create() successful! tid[%lu]\n" , tid);
sgThreadInfo.iAliveThreadCount++;
ilIsHaveIdleThreadFlag = 1;
}
else if(sgThreadInfo.iAliveThreadCount > sgThreadInfo.iWorkThreadCount)
{
printf("Have idle thread! alive[%d]~work[%d]\n" , sgThreadInfo.iAliveThreadCount ,\
sgThreadInfo.iWorkThreadCount);
ilIsHaveIdleThreadFlag = 1;
}
else
{
ilIsHaveIdleThreadFlag = 0;
}
pthread_mutex_unlock(&(sgThreadInfo.mutex));
//存在空闲线程发送信号使其运行,并等待其返回getTask条件
//(若不等待其返回getTask条件,会导致工作线程还未收到singnal时,主进程已进入下一个add_task函数并上锁,
//导致工作线程数量iWorkThreadCount并未增加,数据混乱)
if(ilIsHaveIdleThreadFlag)
{
//有空闲线程,则发送信号执行任务
pthread_mutex_lock(&(sgThreadInfo.mutex));
pthread_cond_signal(&(sgThreadInfo.cond));
//等待工作线程成功获取任务修改工作线程数量并且移动任务链表头结点再进入下次循环,执行后面的任务
pthread_cond_wait(&(sgThreadInfo.condGetTask) , &(sgThreadInfo.mutex));
pthread_mutex_unlock(&(sgThreadInfo.mutex));
}
}
else
pthread_mutex_unlock(&(sgThreadInfo.mutex));
}
return NULL;
}
void sig_clean(int signum)
{
int ilCleanThreadCount = 0;
int i = 0;
pthread_mutex_lock(&(sgThreadInfo.mutex));
if((sgThreadInfo.iWorkThreadCount*2) < sgThreadInfo.iAliveThreadCount && \
sgThreadInfo.iShutdownFlag == 0)
{
if((sgThreadInfo.iWorkThreadCount*2) > sgThreadInfo.iMinThreadCount)
{
ilCleanThreadCount = sgThreadInfo.iAliveThreadCount - \
(sgThreadInfo.iWorkThreadCount*2);
}
else
{
ilCleanThreadCount = sgThreadInfo.iAliveThreadCount - \
sgThreadInfo.iMinThreadCount;
}
}
if(ilCleanThreadCount > 0)
{
printf("********Clean thread start! Current alive thread count[%d] , clean count[%d]********\n" , \
sgThreadInfo.iAliveThreadCount , ilCleanThreadCount);
}
pthread_mutex_unlock(&(sgThreadInfo.mutex));
//添加自杀任务
for(i=0 ; i<ilCleanThreadCount ; i++)
{
add_task(fun_thread_kill_self , NULL);
}
alarm(_CLEAN_TIME_);
}
int destroy_thread_pool()
{
int ilRet = 0;
task_node_t *plCur;
task_node_t *plFreeTmp;
pthread_mutex_lock(&(sgThreadInfo.mutex));
sgThreadInfo.iShutdownFlag = 1;
pthread_mutex_unlock(&(sgThreadInfo.mutex));
//循环直到结束所有线程为止
while(1)
{
pthread_mutex_lock(&(sgThreadInfo.mutex));
if(sgThreadInfo.iAliveThreadCount == 0)
{
pthread_mutex_unlock(&(sgThreadInfo.mutex));
break;
}
//广播唤醒所有的等待线程
pthread_cond_broadcast(&(sgThreadInfo.cond));
pthread_mutex_unlock(&(sgThreadInfo.mutex));
sleep(1);
}
puts("Closed common thread successful!");
ilRet = pthread_cancel(sgThreadInfo.managerTid);
pthread_join(sgThreadInfo.managerTid , NULL);
puts("Closed manager thread successful!");
//释放任务链表
plCur = sgThreadInfo.pTaskHead;
while(plCur)
{
plFreeTmp = plCur;
plCur = plCur->next;
free(plFreeTmp);
}
puts("Free task list successful!");
pthread_mutex_destroy(&(sgThreadInfo.mutex));
pthread_cond_destroy(&(sgThreadInfo.cond));
puts("Destroyed mutex and cond successful!");
return 0;
}
主线程:main.c
注:主线程使用了sleep,为了防止收到管理者线程SIGALRM信号的干扰,这里使用pthread_sigmask函数屏蔽闹钟信号。
#include "thread_pool.h"
int main()
{
int ilRet = 0;
int i = 0;
ilRet = create_thread_pool(5 , 10);
if(ilRet != 0)
{
puts("create_thread_pool() failed");
return -1;
}
//启动18个线程,观察情况
for(i=0 ; i<18 ; i++)
{
arg_struct_t* pTmp = (arg_struct_t *)malloc(sizeof(arg_struct_t));
memset(pTmp , 0 , sizeof(arg_struct_t));
pTmp->iId = i;
if(i<6)
{
pTmp->iAge = 20;
strcpy(pTmp->aName , "James");
strcpy(pTmp->aSex , "male");
}
else if(i<12)
{
pTmp->iAge = 30;
strcpy(pTmp->aName , "Alice");
strcpy(pTmp->aSex , "female");
}
else
{
pTmp->iAge = 40;
strcpy(pTmp->aName , "ZZZZZZZZZ");
strcpy(pTmp->aSex , "male");
}
if(i%2 == 0)
{
add_task(fun_demo1_printIdName , pTmp);
}
else
{
add_task(fun_demo2_printAgeSex , pTmp);
}
}
//屏蔽子线程的alarm信号,避免打断sleep函数
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset,SIGALRM);
pthread_sigmask(SIG_SETMASK , &sigset , NULL);
sleep(65);
destroy_thread_pool();
return 0;
}
Makefile
TARGET = test
DEPEND = thread_pool.o main.o
CC = gcc -g -Wall
RM = rm -f
LIB = -lpthread
$(TARGET) : $(DEPEND)
$(CC) $^ -o $@ $(LIB)
.PHONY : clean
clean :
$(RM) *.o $(TARGET)
生成test运行程序,直接./test运行。由于打印的内容繁多,这里就不贴出来了。终端打印可以看出线程工作状态,存活数量以及工作数量等情况。
执行情况:
这里main线程添加了18个任务,最小线程数为5个最大线程数限制为10个,一开始只有初始化生成的5个线程获取任务,然后管理者创建5个线程达到最大10个线程数,再获取5个任务。前10个线程有5个打印id和姓名,有5个打印年龄和性别。打印年龄和性别的5个线程先运行结束变成空闲线程,然后后续的5个任务紧接着分配到空闲线程中,再然后上一批的打印id和姓名的线程也随着结束,后续任务再补上,交替完成。。。管理者线程每30秒清理一次多余线程,这里60秒之前所有工作线程的任务都执行完了,60秒时会清理掉5个空闲线程,留下最小5个线程待命,最后主线程65秒时发布shutdown指令,销毁所有线程以及管理者线程,程序结束。