Linux下简易线程池
线程池简介
线程池是可以用来在后台执行多个任务的线程集合。 这使主线程可以自由地异步执行其他任务。线程池通常用于服务器应用程序。 每个传入请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理。一旦池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用。 这种重用使应用程序可以避免为每个任务创建新线程的开销。线程池通常具有最大线程数限制。 如果所有线程都繁忙,则额外的任务将放入队列中,直到有线程可用时才能够得到处理。
线程池技术如何提高服务器程序的性能
这里所提到服务器程序是指能够接受客户请求并能处理请求的程序,而不只是指那些接受网络客户请求的网络服务器程序。
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。可以举一个简单的例子:
假设在一台服务器完成一项任务的时间为T
T1 创建线程的时间
T2 在线程中执行任务的时间,包括线程间同步所需时间
T3 线程销毁的时间
显然T = T1+T2+T3。注意这是一个极度简化的假设。
可以看出T1、T3是多线程本身的带来的开销。我们渴望减少T1、T3所用的时间,从而减少T的时间。但一些线程的使用者并没有注意到这一点,所以在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发性)。
线程池技术正是关注如何缩短或调整T1、T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1、T3的开销了。
线程池不仅调整T1、T3产生的时间段,而且它还显著减少了创建线程的数目。在看一个例子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。我们比较利用线程池技术和不利于线程池技术的服务器处理这些请求时所产生的线程总数。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目或者上限(以下简称线程池尺寸),而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池尺寸是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
线程池技术要点
从内部实现上看,线程池技术可主要划分为如下6个要点实现,如下图:
- 工作者线程worker:即线程池中可以重复利用起来执行任务的线程,一个worker的生命周期内会不停的处理多个业务job。线程池“复用”的本质就是复用一个worker去处理多个job,“流控“的本质就是通过对worker数量的控制实现并发数的控制。通过设置不同的参数来控制worker的数量可以实现线程池的容量伸缩从而实现复杂的业务需求
- 待处理工作job的存储队列:工作者线程workers的数量是有限的,同一时间最多只能处理最多workers数量个job。对于来不及处理的job需要保存到等待队列里,空闲的工作者work会不停的读取空闲队列里的job进行处理。基于不同的队列实现,可以扩展出多种功能的线程池,如定制队列出队顺序实现带处理优先级的线程池、定制队列为阻塞有界队列实现可阻塞能力的线程池等。流控一方面通过控制worker数控制并发数和处理能力,一方面可基于队列控制线程池处理能力的上限。
- 线程池初始化:即线程池参数的设定和多个工作者workers的初始化。通常有一开始就初始化指定数量的workers或者有请求时逐步初始化工作者两种方式。前者线程池启动初期响应会比较快但造成了空载时的少量性能浪费,后者是基于请求量灵活扩容但牺牲了线程池启动初期性能达不到最优。
- 处理业务job算法:业务给线程池添加任务job时线程池的处理算法。有的线程池基于算法识别直接处理job还是增加工作者数处理job或者放入待处理队列,也有的线程池会直接将job放入待处理队列,等待工作者worker去取出执行。
- workers的增减算法:业务线程数不是持久不变的,有高低峰期。线程池要有自己的算法根据业务请求频率高低调节自身工作者workers的数量来调节线程池大小,从而实现业务高峰期增加工作者数量提高响应速度,而业务低峰期减少工作者数来节省服务器资源。增加算法通常基于几个维度进行:待处理工作job数、线程池定义的最大最小工作者数、工作者闲置时间。
- 线程池终止:应用停止时线程池要有自身的停止逻辑,保证所有job都得到执行或者抛弃。
简易线程池实现
线程池头文件threadpool.h如下:
#ifndef THREADPOOL_H
#define THREADPOOL_H #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> /**
* 线程体数据结构
*/
typedef struct runner
{
void(*callback)(void* arg); // 回调函数指针
void* arg; // 回调函数的参数
struct runner* next;
} thread_runner; /**
* 线程池数据结构
*/
typedef struct
{
pthread_mutex_t mutex; // 互斥量
pthread_cond_t cond; // 条件变量
thread_runner* runner_head; // 线程池中所有等待任务的头指针
thread_runner* runner_tail; // 线程池所有等待任务的尾指针
int shutdown; // 线程池是否销毁
pthread_t* threads; // 所有线程
int max_thread_size; // 线程池中允许的活动线程数目
} thread_pool; /**
* 线程体
*/
void run(void *arg); /**
* 初始化线程池
* 参数:
* pool:指向线程池结构有效地址的动态指针
* max_thread_size:最大的线程数
*/
void threadpool_init(thread_pool* pool, int max_thread_size); /**
* 向线程池加入任务
* 参数:
* pool:指向线程池结构有效地址的动态指针
* callback:线程回调函数
* arg:回调函数参数
*/
void threadpool_add_runner(thread_pool* pool, void(*callback)(void *arg), void *arg); /**
* 销毁线程池
* 参数:
* ppool:指向线程池结构有效地址的动态指针地址(二级指针),销毁后释放内存,该指针为NULL
*/
void threadpool_destroy(thread_pool** ppool); #endif
线程池实现文件threadpool.c如下:
#include "threadpool.h" #define DEBUG 1 /**
* 初始化线程池
* 参数:
* pool:指向线程池结构有效地址的动态指针
* max_thread_size:最大的线程数
*/
void threadpool_init(thread_pool* pool, int max_thread_size)
{
// 初始化互斥量
pthread_mutex_init(&(pool->mutex), NULL);
// 初始化条件变量
pthread_cond_init(&(pool->cond), NULL);
pool->runner_head = NULL;
pool->runner_tail = NULL;
pool->max_thread_size = max_thread_size;
pool->shutdown = ; // 创建所有分离态线程(即创建线程池)
pool->threads = (pthread_t *)malloc(max_thread_size * sizeof(pthread_t));
int i = ;
for (i = ; i < max_thread_size; i++)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&(pool->threads[i]), &attr, (void*)run, (void*)pool);
}
#ifdef DEBUG
printf("threadpool_init-> create %d detached thread\n", max_thread_size);
#endif
} /**
* 线程体
*/
void run(void *arg)
{
thread_pool* pool = (thread_pool*)arg;
while ()
{
// 加锁
pthread_mutex_lock(&(pool->mutex));
#ifdef DEBUG
printf("run-> locked\n");
#endif
// 如果等待队列为0并且线程池未销毁,则处于阻塞状态(即等待任务的唤醒)
while (pool->runner_head == NULL && !pool->shutdown)
{
pthread_cond_wait(&(pool->cond), &(pool->mutex));
}
//如果线程池已经销毁
if (pool->shutdown)
{
// 解锁
pthread_mutex_unlock(&(pool->mutex));
#ifdef DEBUG
printf("run-> unlocked and thread exit\n");
#endif
pthread_exit(NULL);
}
// 取出链表中的头元素
thread_runner *runner = pool->runner_head;
pool->runner_head = runner->next;
// 解锁
pthread_mutex_unlock(&(pool->mutex));
#ifdef DEBUG
printf("run-> unlocked\n");
#endif
// 调用回调函数,执行任务
(runner->callback)(runner->arg);
free(runner);
runner = NULL;
#ifdef DEBUG
printf("run-> runned and free runner\n");
#endif
}
pthread_exit(NULL);
} /**
* 向线程池加入任务
* 参数:
* pool:指向线程池结构有效地址的动态指针
* callback:线程回调函数
* arg:回调函数参数
*/
void threadpool_add_runner(thread_pool* pool, void(*callback)(void *arg), void *arg)
{
// 构造一个新任务
thread_runner *newrunner = (thread_runner *)malloc(sizeof(thread_runner));
newrunner->callback = callback;
newrunner->arg = arg;
newrunner->next = NULL;
// 加锁
pthread_mutex_lock(&(pool->mutex));
#ifdef DEBUG
printf("threadpool_add_runner-> locked\n");
#endif
// 将任务加入到等待队列中
if (pool->runner_head != NULL)
{
pool->runner_tail->next = newrunner;
// 尾指针指到最后一个任务
pool->runner_tail = newrunner;
}
else
{
pool->runner_head = newrunner;
pool->runner_tail = newrunner;
}
// 解锁
pthread_mutex_unlock(&(pool->mutex));
#ifdef DEBUG
printf("threadpool_add_runner-> unlocked\n");
#endif
// 唤醒一个等待线程
pthread_cond_signal(&(pool->cond));
#ifdef DEBUG
printf("threadpool_add_runner-> add a runner and wakeup a waiting thread\n");
#endif
} /**
* 销毁线程池
* 参数:
* ppool:指向线程池结构有效地址的动态指针地址(二级指针)
*/
void threadpool_destroy(thread_pool** ppool)
{
thread_pool *pool = *ppool;
// 防止2次销毁
if (!pool->shutdown)
{
pool->shutdown = ;
// 唤醒所有等待线程,线程池要销毁了
pthread_cond_broadcast(&(pool->cond));
// 等待所有线程中止
sleep();
#ifdef DEBUG
printf("threadpool_destroy-> wakeup all waiting threads\n");
#endif
// 回收空间
free(pool->threads);
// 销毁等待队列
thread_runner *head = NULL;
while (pool->runner_head != NULL)
{
head = pool->runner_head;
pool->runner_head = pool->runner_head->next;
free(head);
} #ifdef DEBUG
printf("threadpool_destroy-> all runners freed\n");
#endif
// 条件变量和互斥量也别忘了销毁
pthread_mutex_destroy(&(pool->mutex));
pthread_cond_destroy(&(pool->cond)); #ifdef DEBUG
printf("threadpool_destroy-> mutex and cond destoryed\n");
#endif
free(pool);
(*ppool) = NULL; #ifdef DEBUG
printf("threadpool_destroy-> pool freed\n");
#endif
}
}
测试文件如下:
#include "threadpool.h" void threadrun(void* arg)
{
int *i = (int *)arg;
printf("%d\n", *i);
} int main(void)
{
thread_pool *pool = malloc(sizeof(thread_pool));
threadpool_init(pool, ); int i;
int tmp[];
for (i = ; i < ; i++)
{
tmp[i] = i;
threadpool_add_runner(pool, threadrun, &tmp[i]);
} sleep();
threadpool_destroy(&pool);
printf("main-> %p\n", pool);
printf("main-> test over\n"); return ;
}
程序运行结果如下:
参考资料
Linux下简易线程池的更多相关文章
- Linux下简单线程池的实现
大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的.在传统的多线程服务器模型中是这样实现的:一旦有个服务请求到达,就创建一个新的服务 ...
- 一个Linux下C线程池的实现
什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了.如果线程创建和销毁时间相比任 ...
- linux下的线程池
什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了.如果线程创建和销毁时间相比任 ...
- Linux下通用线程池的创建与使用
线程池:简单地说,线程池 就是预先创建好一批线程,方便.快速地处理收到的业务.比起传统的到来一个任务,即时创建一个线程来处理,节省了线程的创建和回收的开销,响应更快,效率更高. 在linux中,使用的 ...
- Linux网络通信(线程池和线程池版本的服务器代码)
线程池 介绍 线程池: 一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的 ...
- Linux下查看线程数的几种方法汇总
Linux下查看线程数的几种方法汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Linux下查看某个进程的线程数量 pstree命令以树状图显示进程间的关系(display ...
- 用java自制简易线程池(不依赖concurrent包)
很久之前人们为了继续享用并行化带来的好处而不想使用进程,于是创造出了比进程更轻量级的线程.以linux为例,创建一个进程需要申请新的自己的内存空间,从父进程拷贝一些数据,所以开销是比较大的,线程(或称 ...
- C++11下的线程池以及灵活的functional + bind + lamda
利用boost的thread实现一个线程类,维护一个任务队列,以便可以承载非常灵活的调用.这个线程类可以方便的为后面的线程池打好基础.线程池还是动态均衡,没有什么别的.由于minGW 4.7 对 C+ ...
- 基于Linux/C++简单线程池的实现
我们知道Java语言对于多线程的支持十分丰富,JDK本身提供了很多性能优良的库,包括ThreadPoolExecutor和ScheduleThreadPoolExecutor等.C++11中的STL也 ...
随机推荐
- springMVC源码分析--BeanNameUrlHandlerMapping(七)
在上一篇博客springMVC源码分析--AbstractDetectingUrlHandlerMapping(五)中我们介绍了AbstractUrlHandlerMapping,其定义了一个抽象函数 ...
- Excel 、数据库 一言不合就转换
Excel 与数据库 对于从事相关行业的小伙伴们而言,可谓是再熟悉不过了,但是面对这两者的转换,你是否已经手忙脚乱,乃至焦头烂额? 还好,今后你将不再受此折磨.不再有日日夜夜加班导入数据的枯燥工作,不 ...
- Python 继承标准类时发生了什么
定义标准类dict的一个子类c: >>> class c(dict): pass >>> y=c({1:2,3:4}) >>> y {1: 2, ...
- Spark技术内幕:Shuffle Read的整体流程
回忆一下,每个Stage的上边界,要么需要从外部存储读取数据,要么需要读取上一个Stage的输出:而下边界,要么是需要写入本地文件系统(需要Shuffle),以供childStage读取,要么是最后一 ...
- spark运算结果写入hbase及优化
在Spark中利用map-reduce或者spark sql分析了数据之后,我们需要将结果写入外部文件系统. 本文,以向Hbase中写数据,为例,说一下,Spark怎么向Hbase中写数据. 首先,需 ...
- 指令汇C电子市场开发(一) ActionBar的使用
前话: 在学习开发谷歌电子市场的的时候,我换了一款比较高大上的模拟器--genymotion,首先去genymotion的官网注册下载,然后安装.感觉这款模拟器运行挺快的,哈哈,而且可以直接把应用拖进 ...
- Spring入门介绍(一)
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架,它主要是为了解决企业应用开发的复杂性而诞生的. 目的:解决企业应用开发的复杂性. 功能:使用基本的javaBean代替EJB. ...
- java的overload与override
概括 方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现.重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个 ...
- windows与linux的文件夹共享
公司配备了一台性能还算不错的电脑,不过是台式机.我在上面装了ubuntu,但是我的代码工作目录全部都在我自己的win7笔记本上.有时程序开多了就容易卡,于是想到用装ubuntu的台式机来访问我win7 ...
- ajax post请求request.getParameter("")取值为null
今天在写提交一个json数据到后台,然后后台返回一个json数据类型.但是发现后台通过request.getParamter("")取到的值为null. 于是写一个简单的ajax ...