多线程编程-线程池技术
2017年10月21日 22:36:49 shuaixio 阅读数:226
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_35692628/article/details/78307089
序言
学习线程池技术的原理与实现。
1. 什么是线程池
-
线程池就是一堆已经创建好的线程,初始都处于空闲等待状态,当有新的任务需要处理的时候,就从这个池子里取一个空闲等待的线程来处理该任务,当处理完成了就再次把该线程放回池中,供后面的任务使用。
-
当池子里的线程全部处于忙碌状态时,线程池中没有可用的空闲等待线程,此时可以通知任务线程池忙稍后再试,或者创建一个新的线程并放入池中。
2. 为什么需要线程池
-
需求:
- 目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。
-
传统处理方式:
- 传统多线程方案中我们采用的服务器模型是:接收到请求,创建线程,由该线程执行该任务,任务执行完毕线程退出。是“即时创建,即时销毁”的策略。
- 相比创建进程的方式,创建线程时间已经大为缩短,但是如果请求频繁的时候服务器就会处于频繁创建线程、频繁销毁线程的状态
-
线程池技术引入
- 如果一个程序每处理一个任务都需要创建一个线程来处理,假设创建线程的时间为T1,任务执行的时间为T2,线程销毁的时间为T3,那么线程的有效使用时间率为 T2 / (T1+T2+T3),如果任务执行的时间非常短,那么线程的使用效率就会非常低,这对高并发的服务器性能来说是不能接受的,所以需要引入线程池概念
3. 线程池工作原理
-
线程池工作原理
- [1] 创建若干线程,放入线程池
- [2] 任务达到时,从线程池中取空闲线程
- [3] 取得空闲线程,立即进行任务处理
- [4] 如果没有取得空闲线程,创建一个线程并放入线程池中,执行[3]
- [5] 如果创建失败或者线程池已满,根据设计策略选择返回错误或将任务放入待处理队列
- [6] 销毁线程池
-
线程同步问题:
-
互斥锁、条件变量、信号量都可以用于线程同步。互斥锁 只能轮询去处理数据,可能造成空转而浪费CPU资源;此外,并发编程建议能使用互斥锁的地方就不要使用 信号量;
-
可以使用 互斥锁 + 条件变量 来解决同步问题,也可利用 多元信号量 进行线程分配设计
-
-
线程分配设计方式一: 互斥锁 + 条件变量
-
本文例子就是采用这种方式实现,每次只有单个线程执行待执行任务
-
采用多元信号量的方式可实现多个线程实现多个任务的处理
-
-
线程分配设计方式二: 多元信号量
-
线程池中每个线程都是等价的。
-
用线程信号量控制子线程和任务的分配问题。设置一个多元信号量来表示任务队列中的任务资源。
-
每个线程都会处于死循环中,每轮循环首先等待一个任务资源信号量,当等到之后互斥地从任务队列中选取一个任务结点,任务结点中记录着该任务所要执行的函数指针和参数。然后子线程开始执行该任务,执行完之后释放一个信号量并进入下一轮循环。当信号量小于1时,子线程将会阻塞。
说明:
- [1] 因为一个任务由一个线程执行,所以要看哪个线程能够获得对应的信号量资源。
- [2] 任务队列由双向链表构造,每个结点包含一个任务的函数指针和参数指针
-
4. 线程池的组成
-
一个简单的线程池有下列组件
-
[1] 线程池管理器:用于创建并管理线程池
-
[2] 任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。用于存放没有处理的任务,提供一种缓冲机制
-
[3] 任务接口:将线程执行的任务抽象出来,每个任务必须实现的接口,以供工作线程调度任务的执行
-
[4] 工作线程:线程池中实际执行的线程
-
5. 线程池的实现:简单方案
// C语言实现方案
// 功能:创建线程池,添加任务,调用空闲线程处理任务,所有任务处理完销毁线程池
/* 基本设定
任务队列:链表实现
任务接口:回调函数,任务计数
线程池管理器:结构体实现
工作线程:显示当前哪个线程在处理哪个任务
*/
/* 详细代码 */
//主要参考:http://blog.csdn.net/zouxinfox/article/details/3560891
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <windows.h>
/* 任务队列 */
//元素:函数,参数指针
typedef struct job
{
void* (*myprocess) (void* arg); /*任务回调函数*/
void* arg; /*回调函数的参数*/
struct job* next; /*任务队列链表*/
} CThread_job;
/*回调函数*/
static void* myprocess(void* arg)
{
printf("threadid: %x, working on task %d\n", pthread_self(), *(int*)arg); //pthread_self(void)获取线程ID
Sleep(1);
return(NULL);
}
/*线程池结构*/
typedef struct thread_pool
{
pthread_mutex_t mutex; /*互斥锁 */
pthread_cond_t cond; /*条件变量 */
CThread_job* queue_head; /*线程池的任务队列*/
int shutdown; /*是否摧毁线程池 0:不摧毁 1:摧毁 */
pthread_t* threadid; /*线程ID数组*/
int max_thread_num; /*线程池最大线程数*/
int cur_queue_size; /*任务队列中待运行任务数目*/
} CThread_pool;
/*线程函数*/
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->mutex), NULL); //互斥锁初始化函数
pthread_cond_init(&(pool->cond), NULL); //条件变量初始化函数
pool->queue_head = NULL;
pool->max_thread_num = max_thread_num;
pool->shutdown = 0; /*0打开1关闭*/
pool->cur_queue_size = 0;
pool->threadid = (pthread_t*)malloc(max_thread_num * sizeof (pthread_t)); //线程ID数组
/*创建工作线程*/
int i = 0;
for (i = 0; i < max_thread_num; ++i)
{
pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL); //创建相同线程
}
}
/*将任务加入队列*/
int pool_add_job(void* (*myprocess) (void* arg), void* arg)
{
/*构造一个新任务*/
printf("thread pool add task arg: %d\n", *(int*)arg);
CThread_job* newjob = (CThread_job*)malloc(sizeof(CThread_job)); //新建任务节点
newjob->myprocess = myprocess;
newjob->arg = arg;
newjob->next = NULL;
/* 线程池的状态被多个线程共享,操作之前需要加锁 */
pthread_mutex_lock(&(pool->mutex)); //上锁,线程池资源绑定执行
/*将任务加入到任务队列中,也就是链表末端*/
CThread_job* job = pool->queue_head;
if (job != NULL)
{
while (job->next != NULL)
job = job->next;
job->next = newjob; //链表新节点
}
else
{
pool->queue_head = newjob;
}
/*是否需要唤醒线程*/
int exeSignal = 0;
if (pool->cur_queue_size == 0)
exeSignal = 1;
pool->cur_queue_size += 1;
pthread_mutex_unlock(&(pool->mutex));
/*需要叫醒工作线程*/
if (exeSignal)
pthread_cond_signal(&(pool->cond));
return 0;
}
/*销毁线程池*/
int pool_destroy()
{
printf("pool destroy now\n");
/*启用关闭开关*/
if (pool->shutdown)
return -1; /*防止两次调用*/
pool->shutdown = 1;
/*唤醒所有等待线程*/
pthread_cond_broadcast(&(pool->cond));
/*阻塞等待线程退出回收资源,还有另一种办法就是线程分离*/
int i;
for (i = 0; i < pool->max_thread_num; ++i)
pthread_join(pool->threadid[i], NULL);
free(pool->threadid);
pool->threadid = NULL;
/*销毁任务队列*/
CThread_job* head = NULL;
while (pool->queue_head != NULL)
{
head = pool->queue_head;
pool->queue_head = pool->queue_head->next;
free(head);
head = NULL;
}
/*销毁互斥锁与条件变量*/
pthread_mutex_destroy(&(pool->mutex));
pthread_cond_destroy(&(pool->cond));
free(pool);
pool = NULL;
printf("pool destroy end\n");
return 0;
}
/*工作线程函数*/
void* thread_routine(void* arg)
{
printf("starting threadid: %x\n", pthread_self());
for (; ;)
{
/* 访问线程池之前需要加锁 */
pthread_mutex_lock(&(pool->mutex));
/*任务队列为空时wait唤醒,当销毁线程池时跳出循环*/
while (pool->cur_queue_size == 0 && !pool->shutdown)
{
printf("threadid: %x is waiting\n", pthread_self());
pthread_cond_wait(&(pool->cond), &(pool->mutex));
}
/*线程池要销毁了*/
if (pool->shutdown) //pthread_cond_broadcast()之后,pool->shutdown置1,销毁线程
{
pthread_mutex_unlock(&(pool->mutex));
printf("threadid: %x will exit\n", pthread_self());
pthread_exit(NULL);
}
/*开始执行任务*/
printf("threadid: %x is starting to work\n", pthread_self());
/*等待队列长度减去1,并取出链表中的头元素*/
pool->cur_queue_size -= 1;
CThread_job* job = pool->queue_head;
pool->queue_head = job->next;
pthread_mutex_unlock(&(pool->mutex));
/*调用回调函数,执行任务*/
(*(job->myprocess)) (job->arg);
free(job);
job = NULL;
}
return(NULL);
}
/*测试*/
int main(int argc, char const *argv[])
{
pool_init(3); /*创建n个线程*/
/*添加n个任务*/
int* jobNum = (int*)malloc(sizeof(int) * 10); /* 一定要动态创建 */
int i;
for (i = 0; i < 5; ++i)
{
jobNum[i] = i;
pool_add_job(myprocess, &jobNum[i]);
}
Sleep(5); /*等待所有任务完成*/
pool_destroy(); /*销毁线程池*/
free(jobNum);
jobNum = NULL;
return 0;
}
线程池的实现:线程池中增加/删除线程
//主要参考:http://blog.csdn.net/u014453898/article/details/53764720
/* 往线程池中增加线程 */
int add_thread(CThread_pool *pool, unsigned int add_threads)
{
if(add_threads == 0)
return 0;
//total_threads: 总线程数,用于循环创建线程时使用
unsigned int total_threads = pool->max_thread_num + add_threads;
//actual_add: 为实际上增加的线程数
int actual_add = 0;
//创建新线程
int i;
for(i = pool->max_thread_num; i < total_threads; i++)
{
//创建线程,并判断是否创建成功,失败提示并退出该函数
if(pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL) != 0)
{
perror("add threads failed!");
if(actual_add == 0)
return -1;
}
actual_add++;
}
//计算增加指定数目的线程后,线程池活动线程数加上实际成功增加的线程数
pool->max_thread_num += actual_add;
return actual_add;
}
/* 从线程池中删除线程 */
int remove_thread(CThread_pool *pool,unsigned int removing_threads)
{
if(removing_threads == 0)
return 0;
int remain_threads = pool->max_thread_num - removing_threads;
remain_threads = remain_threads > 0 ? remain_threads : 1;
int i;
for(i = pool->max_thread_num - 1; i > remain_threads - 1; i--)
{
errno = pthread_cancel(pool->threadid[i]); //删除线程是按照线程创建的顺序来删的
if(errno != 0) //pthread_cancel成功返回0,失败返回错误编号,分立即取消和在可取消点取消
{
break;
}
}
if(i == pool->max_thread_num - 1)
return -1;
else
{
pool->max_thread_num = i + 1;
return pool->max_thread_num;
}
}
6. 线程池的C++实现
-
以上是C语言的线程池实现,C++的接口编程特性和访问权限设置使得多线程多任务的处理更加方便。