首先问三个问题:什么是线程池?线程池的优势是什么?实现原理是什么?
1.线程池的概念
字面意思就是”一个存放线程的池塘”,类似于内存池的概念,创建固定数量的线程,来处理多个任务,线程执行完一个任务后不销毁而是继续执行下一个任务,这样就避免了线程的频繁创建和销毁,大大提高了效率(和多线程相模型相比),这也回答了第二个问题,线程池的优势所在。
2.线程池实现原理
线程池不只有一个线程组,还需要一个任务队列来方便线程池获取任务,此任务队列为被所有线程共享,因此在考虑类的数据结构是必须添加进来,另外,因为有多个线程共享一个资源的情况(都会访问任务队列),还必须要考虑加锁的问题。借用一张图来说明线程池的工作流程:
首先是线程同步的类封装condition.h:
#ifndef MYTHREADPOOL_CONDITION_H
#define MYTHREADPOOL_CONDITION_H
#include <pthread.h>
//封装一个互斥量和条件变量作为状态
typedef struct condition
{
pthread_mutex_t pmutex;
pthread_cond_t pcond;
}condition_t;
class condition_mutex
{
public:
condition_mutex();
~condition_mutex();
//对状态的操作函数
int condition_init();//初始化
int condition_lock();//加锁
int condition_unlock();//解锁
int condition_wait();//等待
int condition_timedwait(const struct timespec *abstime);//超时等待
int condition_signal();//唤醒一个睡眠线程
int condition_broadcast();//唤醒所以睡眠线程
int condition_destroy();//销毁
private:
condition_t c_mutex;
};
#endif //MYTHREADPOOL_CONDITION_H
具体实现condition.cpp:
#include "condition.h"
condition_mutex::condition_mutex()
{
condition_init();
}
condition_mutex::~condition_mutex()
{
condition_destroy();
}
//初始化
int condition_mutex::condition_init()
{
int status;
if((status = pthread_mutex_init(&c_mutex.pmutex, NULL)))
return status;
if((status = pthread_cond_init(&c_mutex.pcond, NULL)))
return status;
return 0;
}
//加锁
int condition_mutex::condition_lock()
{
return pthread_mutex_lock(&c_mutex.pmutex);
}
//解锁
int condition_mutex::condition_unlock()
{
return pthread_mutex_unlock(&c_mutex.pmutex);
}
//等待
int condition_mutex::condition_wait()
{
return pthread_cond_wait(&c_mutex.pcond,&c_mutex.pmutex);
}
//固定时间等待
int condition_mutex::condition_timedwait(const struct timespec *abstime)
{
return pthread_cond_timedwait(&c_mutex.pcond,&c_mutex.pmutex, abstime);
}
//唤醒一个睡眠线程
int condition_mutex::condition_signal()
{
return pthread_cond_signal(&c_mutex.pcond);
}
//唤醒所有睡眠线程
int condition_mutex::condition_broadcast()
{
return pthread_cond_broadcast(&c_mutex.pcond);
}
//释放
int condition_mutex::condition_destroy()
{
int status;
if((status = pthread_mutex_destroy(&c_mutex.pmutex)))
return status;
if((status = pthread_cond_destroy(&c_mutex.pcond)))
return status;
return 0;
}
接下来是线程池结构的封装threadpool.h:
#ifndef MYTHREADPOOL_THREADPOOL_H
#define MYTHREADPOOL_THREADPOOL_H
//线程池头文件
#include "condition.h"
//封装线程池中的对象需要执行的任务对象
typedef struct task
{
void *(*run)(void *args); //函数指针,需要执行的任务
void *arg; //参数
struct task *next; //任务队列中下一个任务
}task_t;
//下面是线程池结构体
typedef struct threadpool_data
{
condition_mutex ready; //状态量
task_t *first; //任务队列中第一个任务
task_t *last; //任务队列中最后一个任务
int counter; //线程池中已有线程数
int idle; //线程池中空闲线程数
int max_threads; //线程池最大线程数
int quit; //是否退出标志
}threadpool_t;
class threadpool
{
public:
threadpool(int threads);
~threadpool();
//线程池初始化
void threadpool_init(int threads);
//往线程池中加入任务
void threadpool_add_task(void *(*run)(void *arg), void *arg);
//摧毁线程池
void threadpool_destroy();
private:
threadpool_t workers;
};
#endif //MYTHREADPOOL_THREADPOOL_H
线程池实现threadpool.cpp:
#include "threadpool.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
//创建的线程执行
static void *thread_routine(void *arg)
{
struct timespec abstime;
int timeout;//超时标志位
printf("thread %d is starting\n",pthread_self());
threadpool_t *pool = (threadpool_t *)arg;
while(1)
{
timeout = 0;
//访问线程池之前需要加锁
pool->ready.condition_lock();
//空闲
pool->idle++;
//等待队列有任务到来 或者 收到线程池销毁通知
while(pool->first == NULL && !pool->quit)
{
//否则线程阻塞等待
printf("thread %d is waiting\n",pthread_self());
//获取从当前时间,并加上等待时间, 设置线程的超时睡眠时间
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += 2;
int status;
status = pool->ready.condition_timedwait(&abstime);//该函数会解锁,允许其他线程访问,当被唤醒时,加锁
if(status == ETIMEDOUT)
{
printf("thread %d wait timed out\n",pthread_self());
timeout = 1;
break;
}
}
pool->idle--;
if(pool->first != NULL)
{
//取出等待队列最前的任务,移除任务,并执行任务
task_t *t = pool->first;
pool->first = t->next;
//由于任务执行需要消耗时间,先解锁让其他线程访问线程池
pool->ready.condition_unlock();
//执行任务
t->run(t->arg);
//执行完任务释放内存
free(t);
//重新加锁
pool->ready.condition_lock();
}
//退出线程池
if(pool->quit && pool->first == NULL)
{
pool->counter--;//当前工作的线程数-1
//若线程池中没有线程,通知等待线程(主线程)全部任务已经完成
if(pool->counter == 0)
{
pool->ready.condition_signal();
}
pool->ready.condition_unlock();
break;
}
//超时,跳出销毁线程
if(timeout == 1)
{
pool->counter--;//当前工作的线程数-1
pool->ready.condition_unlock();
break;
}
pool->ready.condition_unlock();
}
printf("thread %d is exiting\n", pthread_self());
return NULL;
}
threadpool::threadpool(int threads)
{
threadpool_init(threads);
}
threadpool::~threadpool()
{
threadpool_destroy();
}
//线程池初始化
void threadpool::threadpool_init(int threads)
{
workers.ready.condition_init();
workers.first = NULL;
workers.last =NULL;
workers.counter =0;
workers.idle =0;
workers.max_threads = threads;
workers.quit =0;
}
//增加一个任务到线程池
void threadpool::threadpool_add_task(void *(*run)(void *arg), void *arg)
{
//产生一个新的任务
task_t *newtask = (task_t *)malloc(sizeof(task_t));
newtask->run = run;
newtask->arg = arg;
newtask->next=NULL;//新加的任务放在队列尾端
//线程池的状态被多个线程共享,操作前需要加锁
workers.ready.condition_lock();
if(workers.first == NULL)//第一个任务加入
{
workers.first = newtask;
}
else
{
workers.last->next = newtask;
}
workers.last = newtask; //队列尾指向新加入的线程
//线程池中有线程空闲,唤醒
if(workers.idle > 0)
{
workers.ready.condition_signal();
}
//当前线程池中线程个数没有达到设定的最大值,创建一个新的线程
else if(workers.counter < workers.max_threads)
{
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, &workers);
workers.counter++;
}
//结束,访问
workers.ready.condition_unlock();
}
//线程池销毁
void threadpool::threadpool_destroy()
{
//如果已经调用销毁,直接返回
if(workers.quit)
{
return;
}
//加锁
workers.ready.condition_lock();
//设置销毁标记为1
workers.quit = 1;
//线程池中线程个数大于0
if(workers.counter > 0)
{
//对于等待的线程,发送信号唤醒
if(workers.idle > 0)
{
workers.ready.condition_broadcast();
}
//正在执行任务的线程,等待他们结束任务
while(workers.counter)
{
workers.ready.condition_wait();
}
}
workers.ready.condition_unlock();
workers.ready.condition_destroy();
}
测试程序:
#include "threadpool.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void* mytask1(void *arg)
{
printf("thread %d is working on task %d\n", pthread_self(), *(int*)arg);
sleep(1);
free(arg);
return NULL;
}
void* mytask2(void *arg)
{
printf("thread %d is working on task %d\n", pthread_self(), *(int*)arg);
sleep(10);
free(arg);
return NULL;
}
//测试代码
int main(void)
{
//初始化线程池,最多三个线程
threadpool pool(3);
int i;
//创建十个任务
for(i=0; i < 10; i++)
{
if(i == 5)
{
sleep(5);//故意停顿5秒钟,测试线程超时
}
int *arg = (int *)malloc(sizeof(int));
*arg = i;
if (i%2 == 0)
{
pool.threadpool_add_task(mytask1, arg);//偶数执行任务1
}
else
{
pool.threadpool_add_task(mytask2, arg);//奇数执行任务2
}
}
return 0;
}
运行结果分析:
在本测试程序中,主线程(即进程本身)连续创建了三个线程,6139904(暂记为1号线程),6676480(2号线程),7213056(3号线程),且不停地在向任务队列中添加任务,从图中看到,1号线程执行了任务0,2号线程执行了任务1,3号线程执行了任务2,由于奇数任务的执行时间较长(mytask2,休眠10秒钟),1号线程和3号线程结束的比较快,又开始获取新的任务,3号线程获取任务3,进入长时间的休眠中,此时1号线程处理完任务4之后,继续处理任务5,发现任务队列已空,且轮询等待2秒之后没有获取到新的任务,退出线程并销毁。之后5号任务加入了队列,线程池又重新创建了7213056(4号线程),……
从线程的执行时序可以看出,本线程池很好的完成了多任务的线程分配,且执行没有问题,封装简单易用。