概念:
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
具体原理:
首先创建一些线程,将他们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的进程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
使用线程池的原因:
在面向对象编程中创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其他更多资源。因此,提高程序服务效率的一个方法就是尽可能减少创建和销毁对象的次数。如何利用已有对象来服务就是一个需要解决的关键问题,这也是线程池产生的一个重要原因。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
//linux环境中多线程的头文件,非C语言标准库,编译时最后要加 -lpthread 调用动态链接库
#include <pthread.h>
#include <assert.h>
/*
*线程池里所有运行和等待的任务都是一个CThread_worker
*由于所有任务都在链表里,所以是一个链表结构
*/
typedef struct worker
{
/*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/
void *(*process) (void *arg);
void *arg;/*回调函数的参数*/
struct worker *next;
} CThread_worker;
/*线程池结构*/
typedef struct
{
pthread_mutex_t queue_lock;/*互斥锁*/
pthread_cond_t queue_ready;/*条件变量*/
/*链表结构,线程池中所有等待任务*/
CThread_worker *queue_head; /*指向工作链表的头结点*/
/*是否销毁线程池*/
int shutdown;
pthread_t *threadid;/*指向线程ID数组*/
/*线程池中允许的活动线程数目*/
int max_thread_num;
/*当前等待队列的任务数目*/
int cur_queue_size;
}CThread_pool;
/*向线程池中加入任务*/
int pool_add_worker (void *(*process)(void *arg), void *arg);
/*线程函数*/
void *thread_routine (void *arg);
/*一个线程池变量*/
static CThread_pool *pool = NULL;
/*线程池初始化*/
void pool_init (int max_thread_num)
{
//创建线程池
pool = (CThread_pool *)malloc(sizeof(CThread_pool));
//互斥锁初始化
pthread_mutex_init (&(pool->queue_lock), NULL);
//条件变量初始化
pthread_cond_init (&(pool->queue_ready), NULL);
pool->queue_head = NULL;
pool->max_thread_num = max_thread_num;
pool->cur_queue_size = 0;
pool->shutdown = 0;
pool->threadid = (pthread_t *)malloc(max_thread_num * sizeof (pthread_t));
int i = 0;
for (i = 0; i < max_thread_num; i++)
{
//创建线程,参数为线程ID变量地址、线程属性、线程函数、线程参数
pthread_create(&(pool->threadid[i]), NULL, thread_routine,NULL);
}
}
/*向线程池中加入任务*/
int pool_add_worker (void *(*process)(void *arg), void *arg)
{
/*构造一个新任务*/
CThread_worker *newworker = (CThread_worker *)malloc(sizeof(CThread_worker));
newworker->process = process;
newworker->arg = arg;
newworker->next = NULL;/*别忘置空*/
//加锁
pthread_mutex_lock(&(pool->queue_lock));
/*将任务加入到等待队列中*/
CThread_worker *member = pool->queue_head;
if (member != NULL)
{
while(member->next != NULL)
member = member->next;
member->next = newworker;
}
else
{
pool->queue_head = newworker;
}
//断言
assert(pool->queue_head != NULL);
pool->cur_queue_size++;
//解锁
pthread_mutex_unlock(&(pool->queue_lock));
/*好了,等待队列中有任务了,唤醒一个等待线程
注意如果所有线程都在忙碌,这句没有任何作用*/
pthread_cond_signal(&(pool->queue_ready));
return 0;
}
/*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直
把任务运行完后再退出*/
int pool_destroy()
{
if(pool->shutdown)
return -1;/*防止两次调用*/
pool->shutdown = 1;
/*唤醒所有等待线程,线程池要销毁了*/
pthread_cond_broadcast(&(pool->queue_ready));
/*阻塞等待线程退出,否则就成僵尸了*/
int i;
for (i = 0; i < pool->max_thread_num; i++)
pthread_join (pool->threadid[i], NULL);
free(pool->threadid);
/*销毁等待队列*/
CThread_worker *head = NULL;
while (pool->queue_head != NULL)
{
head = pool->queue_head;
pool->queue_head = pool->queue_head->next;
free(head);
}
/*条件变量和互斥量也别忘了销毁*/
pthread_mutex_destroy(&(pool->queue_lock));
pthread_cond_destroy(&(pool->queue_ready));
free (pool);
/*销毁后指针置空是个好习惯*/
pool=NULL;
return 0;
}
/*线程函数*/
void* thread_routine(void *arg)
{
//输出线程ID
printf("starting thread 0x%x\n", pthread_self());
while(1)
{
//加锁
pthread_mutex_lock (&(pool->queue_lock));
/*如果等待队列为0并且不销毁线程池,则处于阻塞状态; 注意
pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
//如果没有任务就等待
while(pool->cur_queue_size == 0 && !pool->shutdown)
{
printf("thread 0x%x is waiting\n", pthread_self ());
//等待资源,条件变量用于通知。会释放第二个参数的锁,以供添加;函数返回时重新加锁。
pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
}
/*线程池要销毁了*/
if (pool->shutdown)
{
/*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/
pthread_mutex_unlock(&(pool->queue_lock));
printf ("thread 0x%x will exit\n", pthread_self());
pthread_exit(NULL);
}
printf("thread 0x%x is starting to work\n", pthread_self());
/*assert是调试的好帮手*/
assert(pool->cur_queue_size != 0);
assert(pool->queue_head != NULL);
/*等待队列长度减去1,并取出链表中的头元素*/
pool->cur_queue_size--;
CThread_worker *worker = pool->queue_head;
pool->queue_head = worker->next;
pthread_mutex_unlock(&(pool->queue_lock));
/*调用回调函数,执行任务*/
(*(worker->process))(worker->arg);
free(worker);
worker = NULL;
}
/*这一句应该是不可达的*/
pthread_exit (NULL);
}
//测试代码
void * myprocess(void *arg)
{
printf("threadid is 0x%x, working on task %d\n", pthread_self(),*(int *)arg);
sleep(1);/*休息一秒,延长任务的执行时间*/
return NULL;
}
int main(int argc, char **argv)
{
pool_init(3);/*线程池中最多三个活动线程*/
/*连续向池中投入10个任务*/
int *workingnum = (int *)malloc(sizeof(int) * 10);
int i;
for (i = 0; i < 10; i++)
{
workingnum[i] = i;
pool_add_worker(myprocess, &workingnum[i]);
}
/*等待所有任务完成*/
sleep(5);
/*销毁线程池*/
pool_destroy();
free(workingnum);
return 0;
}
线程等待任务的到来
等待任务
注:线程池的伸缩性对性能有较大的影响
(1)创建太多线程,将会浪费一定资源,有些线程未被充分使用;
(2)销毁太多线程,将导致之后浪费时间再次创建它们;
(3)创建线程太慢,将会导致长时间的等待,性能变差;
(4)销毁线程太慢,导致其他线程资源饥饿。