线程池多任务_逻辑架构整理
线程池多任务_逻辑架构整理
01.为什么需要线程池
首先,线程的创建需要内存资源,线程的创建和销毁需要时间资源。
其次,由于系统资源的限制,多线程成了实现多任务处理的重要方式。在一些常见的应用环境中,如Web服务器,Email服务器以及数据库服务器等都具有一个共同点:单位时间必须处理很多并发的连接请求,但处理时间却相对较短。
传统多线程方案中我们采用的服务器模型则是一旦接收到请求之后,即创建一个新的线程,该线程去执行任务,任务执行完毕后,线程退出,这就是“即使创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,但执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。
线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到多个任务上。其好处是请求到达时,线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。
线程池中的线程是有上界的,也就是当请求的数目超过某个阈值时,就强制其它任务新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足的问题。
考虑一下当前请求很少,线程池中的线程个数很多即一部分线程处于空闲状态,如何将这部分空闲的线程回收?做到线程池的自动收缩。
02 本文将给出一种任务队列控制的线程池模型的具体实现
任务队列控制的线程池模型是通过任务队列来对线程池进行并发调度,如下图所示。线程池是由预创建的一个任务队列和一组工作线程组成,其中任务队列存放工作对象。线程池启动后,工作线程将采用轮询的方式从任务队列中获取任务对象,由于初始化时任务队列中不存在任务对象,这时的信号量为0,所有的工作线程都处于阻塞状态。主线程将任务对象放入任务队列中,并将信号量加1,这样信号量就会唤醒一个阻塞状态的工作线程(操作系统层面决定唤醒哪个阻塞的工作线程)。工作线程被唤醒后从任务队列中获取一个任务对象并执行该任务,任务执行完后,工作线程将再次访问信号量,如果信号量大于0,那么工作线程将继续从任务队列中获取任务对象并执行,直到信号量等于0,这时工作线程将再次被阻塞。

03 线程池主要由以下三个部分组成:
① 任务队列,用途:将新任务添加到任务队列的末尾,并通知空闲线程可以从队列最前端取任务并执行。
② 线程队列,用途:用来存放被创建的线程,这些线程主要由处于两种状态:正在执行任务的状态(运行状态)和正在等待分配任务(阻塞或等待状态)。
③ 控制器,管理一个队列锁和一个信号量。
队列锁——因为多个线程对同一任务队列进行任务取用的时候,会有数据竞争(Data Race),所以对任务队列进行存、取操作的时候都需要加锁,处理完成后需解锁。[c1]
信号量——在任务队列有新任务的时候,一旦启用信号量,某一处于阻塞的线程同时获取队列锁和信号量从而解阻塞->取用任务->执行。
/*
[c1] Pthread_mutex_lock()的作用实际就是上锁,这个函数和pthread_mutex_unlock 解锁配套使用。两句函数中间的代码就是被上锁的代码,被上锁的代码只能有一个线程使用,别的线程执行到这里会发生阻塞,只有pthread_mutex_unlock之后,别的线程才能使用pthread_mutex_lock之后进入代码。
// pthread_cond_wait( ) 如果条件不满足则会阻塞线程。
pthread_cond_wait( )内部的操作顺序是将线程放到等待队列,然后解锁,等条件满足时进行加锁,然后返回;【此处注意:解锁后,条件满足时进行加锁,这个锁仍归当前线程所持有,别的线程执行到此代码处发现这个锁仍存在,则别的线程会阻塞】
例:pthread_cond_wait(&(queue_ready), &(queue_lock));
如果当前线程获得了该锁,但没有获得信号量通知—>解锁并阻塞;
pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
pthread_cond_broadcast()函数会将所有等待该条件变量的线程解锁而不是仅仅解锁一个线程。
*/
三个主要的方法:
1) 添加任务到任务队列-- add_task()
2) 通知线程有新任务-- _run_task()
3) 销毁线程池开辟的所有资源结束该批次多任务处理 –destroy_pool()
04 pthread_create函数的详细讲解(包括向线程函数传递参数详解)
头文件
#include<pthread.h>
函数声明
int pthread_create(pthread_t * restrict tidp,const pthread_attr_t * restrict_attr,void * (*start_rtn)(void *),void *restrict arg)
返回值
若成功则返回0,否则返回出错编号
返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构体的地址作为arg的参数传入。
linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
由 restrict 修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的指针表达式中。 由 restrict 修饰的指针主要用于函数形参,或指向由 malloc() 分配的内存空间。restrict 数据类型不改变程序的语义。 编译器能通过作出 restrict 修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。
参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
另外,在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
向线程函数传递参数详解,向线程函数传递参数分为两种:
(1)线程函数只有一个参数的情况:直接定义一个变量通过应用传给线程函数。
例子:
#include <iostream>
#include <pthread.h>
using namespace std;
pthread_t thread;
void fn(void *arg)
{
int i = *(int *)arg;
cout<<"i = "<<i<<endl;
return ((void *)0);
}
int main()
{
int err1;
int i=10;
err1 = pthread_create(&thread, NULL, fn, &i);
pthread_join(thread, NULL);
}
2、线程函数有多个参数的情况:这种情况就必须申明一个结构体来包含所有的参数,然后在传入线程函数,具体如下:
例子:
首先定义一个结构体:
struct parameter
{
int size,
int count;
};
然后在main函数将这个结构体指针,作为void *形参的实际参数传递struct parameter arg;
通过如下的方式来调用函数:pthread_create(&ntid, NULL, fn,& (arg));函数中需要定义一个parameter类型的结构指针来引用这个参数
void fn(void *arg)
{
int i = *(int *)arg;
cout<<"i = "<<i<<endl;
return ((void *)0);
}
void thr_fn(void *arg)
{
struct parameter *pstru;
pstru = ( struct parameter *) arg;
然后在这个函数中就可以使用指针来使用相应的变量的值了。
}
05 任务链表结构体(tpool_work_t)和线程池结构体(tpool_t)
【testpool.c // 线程池测试程序】
#include <pthread.h>
#include "log.h"
#include "tpool.h"
log_t *log; /*进程全局日志文件句柄*
/*任务*/
void thread(void *arg)
{
char * ptr=(char *)arg;
sleep(1);
printf("hello world! %s\n",ptr);
}
int main(int argc, char *argv[])
{
tpool_t *pool; /*线程池指针*/
/* 开启记录文件 */
log=log_open("test.log", 0);
/* 创建一个有100个工作线程,最大200个任务队列的线程池 */
pool=tpool_init(100,200,1);
int i;
/* 开启记录文件 */
* 添加100个任务*/
for (i = 0; i<100;i++)
tpool_add_work(pool,thread,"test!");
sleep(10);
/*终止线程池*/
tpool_destroy(pool,1);
/* 关闭记录文件 */
log_close(log);
pthread_exit(NULL);
}
【* tpool.h //线程池定义】
#ifndef _TPOOL_H_
#define _TPOOL_H_
#include <stdio.h>
#include <pthread.h>
/*任务链表结构体*/
typedef struct tpool_work
{
void (*handler_routine)(); /*任务函数指针*/
void *arg; /*任务函数参数*/
struct tpool_work *next; /*下一个任务链表*/
} tpool_work_t[c1] ; /* [c1]任务链表结构体,任务节点Node.*/
/*线程池结构体*/
typedef struct tpool
{
int num_threads; /*最大线程数*/
int max_queue_size; /*最大任务链表数*/
int do_not_block_when_full; /*当链表满时是否阻塞*/
pthread_t *threads; /*线程指针*/
int cur_queue_size;
tpool_work_t *queue_head; /*链表头*/
tpool_work_t *queue_tail; /*链表尾*/
pthread_mutex_t queue_lock; /*链表互斥量*/
pthread_cond_t queue_not_full; /*链表条件量-未满*/
pthread_cond_t queue_not_empty; /*链表条件量-非空*/
pthread_cond_t queue_empty; /*链表条件量-空*/
int queue_closed;
int shutdown;
} tpool_t;[c1] /*[c1]线程池结构体。*/
/* 初始化连接池 */
extern tpool_t *tpool_init(int num_worker_threads,int max_queue_size, int do_not_block_when_full);
/* 添加一个工作任务*/
extern int tpool_add_work(tpool_t *pool, void (*routine)(), void *arg);
/* 清除线程池*/
extern int tpool_destroy(tpool_t *pool, int finish);
#endif /* _TPOOL_H_ */
【线程池初始化tpool_init()】
tpool_t *tpool_init(int num_worker_threads,int max_queue_size, int do_not_block_when_full)
{ /*线程池线程个数*/ /*最大任务数*/ /*是否阻塞任务满的时候*/
int i, rtn;
tpool_t *pool[c1] ; /* [c1]【声明线程池结构体变量 *pool】*/
lprintf(log, INFO, "init pool begin ...\n");
/* 创建线程池结构体 */
if((pool = (struct tpool *)malloc(sizeof(struct tpool))) == NULL[c2] ) /*[c2]【给声明的线程池结构体变量*pool 赋值】*/
{
lprintf(log, FATAL, "Unable to malloc() thread pool!\n");
return NULL;
}
/* 设置线程池架构体成员 */
pool->num_threads = num_worker_threads; /*工作线程个数*/
pool->max_queue_size = max_queue_size; /*任务链表最大长*/
pool->do_not_block_when_full = do_not_block_when_full; /*任务链表满时是否等待*/
/* 生成线程池缓存 */
/*pthread_t *threads; 线程指针*/[c1] /* [c1]该条语句在线程池结构体中可以查到*/
if((pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*num_worker_threads)[c2] ) == NULL)
/*
[c2]【申请num_worker_threads个线程指针大小的内存单元,并返回分配内存单元的起始地址】
c语言 int *a = (int*)malloc(n*sizeof(int)); 是什么意思呀?
int *a=(int *)malloc(n*sizeof(int)); 表示定义一个int类型的指针变量a,并申请n*sizeof(int)个字节(即4*n个字节)的存储空间。
malloc是在C语言中是一个申请内存单元的函数。
函数原型:void *malloc(unsigned size);
功 能:分配size个字节的内存空间
返 回 值:成功,返回分配的内存单元的起始地址;否则返回0
(int)a表示强制转换,把a强制转换成int类型。
*/
{
lprintf(log, FATAL,"Unable to malloc() thread info array\n");
return NULL;
}
/* 初始化任务链表 */
pool->cur_queue_size = 0; //当前任务队列的大小
pool->queue_head = NULL; //任务队列的头指针
pool->queue_tail = NULL; //任务队列的尾指针
pool->queue_closed = 0;
pool->shutdown = 0;
/* 初始化互斥变量,条件变量 用于线程之间的同步 */
if((rtn = pthread_mutex_init(&(pool->queue_lock)[c1] ,NULL)) != 0) /* [c1]初始化:任务队列互斥锁*/
{
lprintf(log,FATAL,"pthread_mutex_init %s",strerror(rtn));
return NULL;
}
if((rtn = pthread_cond_init(&(pool->queue_not_empty)[c2] ,NULL)) != 0) /* [c2]初始化:任务队列条件量--非空*/
{
lprintf(log,FATAL,"pthread_cond_init %s",strerror(rtn));
return NULL;
}
if((rtn = pthread_cond_init(&(pool->queue_not_full)[c3] ,NULL)) != 0) /* [c3]初始化:任务队列条件量—非满*/
{
lprintf(log,FATAL,"pthread_cond_init %s",strerror(rtn));
return NULL;
}
if((rtn = pthread_cond_init(&(pool->queue_empty)[c4] ,NULL)) != 0) /* [c4]初始化:任务队列条件量—空*/
{
lprintf(log,FATAL,"pthread_cond_init %s",strerror(rtn));
return NULL;
}
/* 创建所有的线程 */
for(i = 0; i != num_worker_threads; i++)
{
if( (rtn=pthread_create(&(pool->threads[i]),NULL,tpool_thread,(void*)pool))[c5] != 0) { /* [c5]//【最后一个参数为(void*)pool】*/
lprintf(log,FATAL,"pthread_create %s\n",strerror(rtn));
return NULL;
}
lprintf(log, INFO, "init pthread %d!\n",i);
}
lprintf(log, INFO, "init pool end!\n");
return pool;
}
【/*工作线程*/--在任务队列中取出一个任务节点交给工作线程处理】
void *tpool_thread(void *tpool) [c1]
/*
[c1]函数tpool_thread定义了工作线程的函数,其中真正与实际任务有关的只有一行代码:
(*(my_work->handler_routine))(my_work->arg);即执行my_work->handler_routine指针指向的函数,并传入参数my_work->arg.其他的步骤都是为执行这个任务而进行的各种设置和准备。
*/
{
tpool_work_t *my_work; //【根据工作线程结构体变量定义*my_work】
tpool_t *pool = (struct tpool *)tpool;
for(;;)
{
/* 线程内循环 */
pthread_mutex_lock(&(pool->queue_lock)); //【获取任务队列锁】
/* 如果任务列表为0,并且线程池没有关闭,则一直等待,直到任务到来为止 */
while((pool->cur_queue_size == 0) && (!pool->shutdown[c1] ))
{
pthread_cond_wait(&(pool->queue_not_empty), &(pool->queue_lock));
}
/* 线程池是否已经关闭,如果线程池关闭则线程自己主动关闭 */
if(pool->shutdown)
{
pthread_mutex_unlock(&(pool->queue_lock));
pthread_exit(NULL); /*线程退出状态为空,主线程不捕获各副线程状态*/
}
my_work = pool->queue_head; //【将任务队列头结点赋值给my_work】
pool->cur_queue_size--;
/*将任务链表头部去掉,改任务正在处理中*/
if(pool->cur_queue_size == 0)
pool->queue_head = pool->queue_tail = NULL;
else
pool->queue_head = my_work->next; //【将任务链表头部去掉】
/* 任务链表还没有满 */
if((!pool->do_not_block_when_full)&&(pool->cur_queue_size== (pool->max_queue_size-1))) {
pthread_cond_broadcast(&(pool->queue_not_full));
}
/*任务链表为空*/
if(pool->cur_queue_size == 0)
{
pthread_cond_signal(&(pool->queue_empty));
}
pthread_mutex_unlock(&(pool->queue_lock));
/*启动线程业务处理逻辑*/
(*(my_work->handler_routine))(my_work->arg);
free(my_work);
}
return(NULL);
}
/*
While(循环判断条件)
循环判断条件:
pool->cur_queue_size当前任务队列的大小为0即任务队列为空。
!pool->shutdown 线程池不销毁。
pthread_cond_wait(&(pool->queue_not_empty), &(pool->queue_lock));
while循环条件为真时(当前任务队列为空)
进入到while执行语句,pthread_cond_wait(&(pool->queue_not_empty), &(pool->queue_lock));
① 如果queue_not_empty 一直没有来到,pthread_cond_wait()内存操作逻辑是:
1.将当前线程放入等待队列(操作系统层面,阻塞);2.执行pthread_mutex_unlock(&(pool->queue_lock));
这样别的线程就能获取到pool->queue_lock
② 解的锁pool->queue_lock会在各个线程间传递;1.如果queue_not_empty到来了pthread_cond_wait()会返回(锁和等待条件来到后,之前阻塞的线程会被激活运行);
2. pthread_cond_wait()返回后,会重新加锁。
*/
函数tpool_add_work为线程池添加了一个工作任务。
int tpool_add_work(tpool_t *pool, void (*routine)(void *),void *arg)[c1] /* [c1]函数tpool_add_work为线程池添加了一个工作任务。因为预创建的线程是不能做任何工作的,只有分配了适当的任务后,才会使预创建的线程真正的工作起来。*/
/*线程池指针*/ /*工作线程函数指针*/ /*工作线程函数参数*/
int tpool_add_work(tpool_t *pool, void (*routine)(void *),void *arg)
{
int rtn;
tpool_work_t *workp; /*当前工作线程*/
if((rtn = pthread_mutex_lock(&pool->queue_lock)) [c1] != 0) /* [c1]任务队列锁加锁*/
{
lprintf(log,FATAL,"pthread mutex lock failure\n");
return -1;
}
/* 采取独占的形式访问任务链表 */
if((pool->cur_queue_size == pool->max_queue_size) && (pool->do_not_block_when_full))
{
if((rtn = pthread_mutex_unlock(&pool->queue_lock)) != 0)
{
lprintf(log,FATAL,"pthread mutex lock failure\n");
return -1;
}
return -1;
}
/* 等待任务链表为新线程释放空间 */
while((pool->cur_queue_size==pool->max_queue_size) && (!(pool->shutdown || pool->queue_closed)))
{
if((rtn = pthread_cond_wait(&(pool->queue_not_full),&(pool->queue_lock)) ) != 0)
{
lprintf(log,FATAL,"pthread cond wait failure\n");
return -1;
}
}
if(pool->shutdown || pool->queue_closed)
{
if((rtn = pthread_mutex_unlock(&pool->queue_lock)) != 0)
{
lprintf(log,FATAL,"pthread mutex lock failure\n");
return -1;
}
return -1;
}
/* 分配工作线程结构体 */
if((workp = (tpool_work_t *)malloc(sizeof(tpool_work_t))) == NULL)
{
lprintf(log,FATAL,"unable to create work struct\n");
return -1;
}
workp->handler_routine = routine;
workp->arg = arg;
workp->next = NULL;
if(pool->cur_queue_size == 0)
{
pool->queue_tail = pool->queue_head = workp;
if((rtn = pthread_cond_broadcast(&(pool->queue_not_empty))) != 0)
{
lprintf(log,FATAL,"pthread broadcast error\n");
return -1;
}
}
else
{
pool->queue_tail->next = workp;
pool->queue_tail = workp;
}
pool->cur_queue_size++;
/* 释放对任务链表的独占 */
if((rtn = pthread_mutex_unlock(&pool->queue_lock)) != 0)
{
lprintf(log,FATAL,"pthread mutex lock failure\n");
return -1;
}
return 0;
}

https://blog.csdn.net/ce123_zhouwei/article/details/11705479?utm_source=blogxgwz3

浙公网安备 33010602011771号