系统编程八:线程池

一、分析线程池文件

pool_test/  -> 线程池的源码
        thread_pool.c  -> 线程池函数接口源码  -> 待会主要分析这个文件
        thread_pool.h  -> 线程池函数声明、线程池结构体..
        main.c   -> 简单应用。
线程池接口设计说明书.doc  -> 详细说明了线程池中函数的用法。

二、线程池实现过程

1、 什么是线程池?

        线程池就是多个线程组合起来的一个集合,就好像一家公司,由多个员工组成的一个集合。当有任务时,这些线程就会去处理这些任务,当没有任务时,线程就会休息。

2、 如何来描述一个线程池?  -> 使用一个结构体

typedef struct thread_pool
{
	pthread_mutex_t lock;    -> 互斥锁
	pthread_cond_t  cond;    -> 条件变量
	bool shutdown;		 -> 当前线程池的标志位  true -> 线程池关闭了,  false  -> 线程池没有关闭。
	struct task *task_list;  -> 任务队列的头节点
	pthread_t *tids;         -> 储存所有线程的ID号
	//unsigned max_waiting_tasks;  -> 最大等待任务的数目
	unsigned waiting_tasks;	     -> 当前正在等待处理的任务的个数//链表中有多少个结点,每个结点就是一个任务,也就是当前有多少个任务
	unsigned active_threads;     -> 活跃的线程个数 //表示当前线程池里面有多少条线程
	
}thread_pool;

//任务队列 的结点数据类型---- 也就是说 任务结构体的数据类型
struct task
{
	/* 数据域 */
	void *(*do_task)(void *arg); //函数指针 -> 这个任务需要执行的函数,这个函数必须是: void *fun(void *arg)
	void *arg;    -> 传递给执行的函数的参数

	/* 指针域 */
	struct task *next;  -> 指向下一个任务节点的指针。
};

3、 如何实现初始化?  -> init_pool()

bool init_pool(thread_pool *pool, unsigned int threads_number)
{
	//1. 初始化线程池互斥锁
	pthread_mutex_init(&pool->lock, NULL);
	
	//2. 初始化条件变量
	pthread_cond_init(&pool->cond, NULL);

	//3. 初始化标志位为false,代表当前线程池正在运行。
	pool->shutdown = false;
	
	//4. 初始化任务队列的头节点
	pool->task_list = malloc(sizeof(struct task));
	
	//5. 为储存线程ID号申请空间。
	pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);

	//第4步与第5步错误判断
	if(pool->task_list == NULL || pool->tids == NULL)
	{
		perror("allocate memory error");
		return false; //初始化线程池失败
	}

	//6. 为线程池任务队列的头节点的指针域赋值NULL
	pool->task_list->next = NULL;
	
	//7. 设置线程池最大任务个数为1000
	pool->max_waiting_tasks = MAX_WAITING_TASKS;
	
	//8. 当前需要处理的任务为0
	pool->waiting_tasks = 0;
	
	//9. 初始化线程池中线程池的个数
	pool->active_threads = threads_number;

	//10. 创建线程
	int i;
	for(i=0; i<pool->active_threads; i++)
	{
		if(pthread_create(&((pool->tids)[i]), NULL,routine, (void *)pool) != 0)
		{
			perror("create threads error");
			return false;
		}
	}

	//11. 线程池初始化成功
	return true;
}

总结:

        初始化线程池函数其实就是给线程池的结构体赋值,将一些变量赋初值,以及初始化变量,为指针申请空间。

4、如何添加任务呢?  ->  add_task()

bool add_task(thread_pool *pool,void *(*do_task)(void *arg), void *arg)
{
	//1. 为新任务的节点申请空间
	struct task *new_task = malloc(sizeof(struct task));
	if(new_task == NULL)
	{
		perror("allocate memory error");
		return false;
	}
	
	//2. 为新节点的数据域与指针域赋值
	new_task->do_task = do_task;  
	new_task->arg = arg; 
	new_task->next = NULL;  
	
	//3.  在添加任务之前,必须先上锁,因为添加任务属于访问临界资源 -> 任务队列
	pthread_mutex_lock(&pool->lock);
	
	//4. 如果当前需要处理的任务个数>=1000
	if(pool->waiting_tasks >= MAX_WAITING_TASKS)
	{
		//解锁
		pthread_mutex_unlock(&pool->lock);
		
		//打印一句话提示一下,太多任务了
		fprintf(stderr, "too many tasks.\n");
		
		//释放掉刚刚准备好的新节点
		free(new_task);

		return false;
	}
	
	//5. 寻找任务队列中的最后一个节点
	struct task *tmp = pool->task_list;
	while(tmp->next != NULL)
		tmp = tmp->next;
	//从循环中出来时,tmp肯定是指向最后一个节点
	
	//6. 让最后一个节点的指针域指向新节点
	tmp->next = new_task;
	
	//7. 当前需要处理的任务+1
	pool->waiting_tasks++;

	//8. 添加完毕,就解锁。
	pthread_mutex_unlock(&pool->lock);

	//9. 唤醒条件变量中的一个线程起来做任务
	pthread_cond_signal(&pool->cond);
	
	return true;
}

总结:

插入一个新节点到任务队列的末尾,唤醒条件变量中的一个线程起来做任务。

5、线程例程函数  -> void *routine(void *arg)

void *routine(void *arg)
{
	//1. 先接住线程池的地址
	thread_pool *pool = (thread_pool *)arg;
	struct task *p;

	while(1)
	{
		//2. 线程取消例程函数
		//将来要是有人要取消我,请先把锁解开,然后再响应取消。
		pthread_cleanup_push(handler, (void *)&pool->lock);
		
		//3. 上锁
		pthread_mutex_lock(&pool->lock);
		
		//如果当前线程池没有关闭,并且当前线程池没有任务做
		while(pool->waiting_tasks == 0 && !pool->shutdown)
		{
			//那么就进去条件变量中睡觉。
			pthread_cond_wait(&pool->cond, &pool->lock); //自动解锁
		}
		
		//要是线程池关闭了,或者有任务做,这些线程就会运行到这行代码
		//判断当前线程池是不是关闭了,并且没有等待的任务
		if(pool->waiting_tasks == 0 && pool->shutdown == true)
		{	
			//如果线程池关闭,又没有任务做
			//线程那么就会解锁
			pthread_mutex_unlock(&pool->lock);	
			
			//线程走人
			pthread_exit(NULL); 
		}
		
		//能运行到这里,说明有任务做
		//p指向头节点的下一个
		p = pool->task_list->next;
		
		//让头节点的指针域指向p的下一个节点
		pool->task_list->next = p->next;
		
		//当前任务个数-1
		pool->waiting_tasks--;

		//解锁
		pthread_mutex_unlock(&pool->lock);
		
		//删除线程取消例程函数
		pthread_cleanup_pop(0);
		
		//设置线程不能响应取消
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
		
		//执行这个p节点指向的节点的函数
		(p->do_task)(p->arg);
		
		//设置线程能响应取消
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

		//释放p对应的空间
		free(p);
	}

	pthread_exit(NULL);
}

总结:

        线程去任务队列中拿一个节点出来,就执行该任务(过程不能被取消),如果没有任务做,就去睡觉。

6、添加线程  -> add_thread()

int add_thread(thread_pool *pool, unsigned additional_threads)
{
	//如果说你想添加0条,则直接返回0。
	if(additional_threads == 0)
		return 0; 

	unsigned total_threads = pool->active_threads + additional_threads;
			 //total_threads = 原本 2条 + 现在再加2条
						
	int i, actual_increment = 0;
	
	// i = 2 ; i<4 && i<20; i++
	for(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS;  i++) 
	{
		if(pthread_create(&((pool->tids)[i]),NULL, routine, (void *)pool) != 0)
		{
			perror("add threads error");
			if(actual_increment == 0) 
				return -1;

			break;
		}
		actual_increment++;  //真正创建线程的条数
	}

	pool->active_threads += actual_increment; //当前线程池线程个数 = 原来的个数 + 实际创建的个数
	
	return actual_increment; //返回真正创建的个数
}

总结:

根据用户的需要去创建线程,但是不能超过20条。

7、删除线程  -> remove_thread()

int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
	//1. 如果你想删0条,直接返回。
	if(removing_threads == 0)
		return pool->active_threads;  //返回当前剩余的线程数

	int remaining_threads = pool->active_threads - removing_threads;
	//            3         =            5         -  2
	//			  0			=	         5         -  5
	//           -3         =            5         -  8

	remaining_threads = remaining_threads > 0 ? remaining_threads : 1;

	int i;  
	for(i=pool->active_threads-1; i>remaining_threads-1; i--)
	{	
		errno = pthread_cancel(pool->tids[i]); //取消这些线程
		if(errno != 0)
			break;
	}

	if(i == pool->active_threads-1) //删除失败
		return -1;
	else
	{
		pool->active_threads = i+1; //当前实际线程个数
		return i+1; 
	}
}

总结:

根据用户需要,去取消掉一些线程,但是线程池中线程不能小于1条。

8、销毁线程池。  ----> destroy_pool() 

bool destroy_pool(thread_pool *pool)
{
	//1. 设置线程池关闭标志为真
	pool->shutdown = true; 
	
	pthread_cond_broadcast(&pool->cond);  //目的: 就是让线程退出!
	
	//2. 接合所有的线程
	int i;
	for(i=0; i<pool->active_threads; i++)
	{
		errno = pthread_join(pool->tids[i], NULL);

		if(errno != 0)
		{
			printf("join tids[%d] error: %s\n",
					i, strerror(errno));
		}
	
		else
			printf("[%u] is joined\n", (unsigned)pool->tids[i]);
		
	}

	//3. 释放一些空间
	free(pool->task_list);
	free(pool->tids);
	free(pool);

	return true;
}

总结: 唤醒所有的小孩,让小孩走人。

三、实例

1、main.c

#include "thread_pool.h"

void *mytask(void *arg) //线程的任务
{
	int n = *(int*)arg;

	//工作任务:余数是多少,就睡多少秒,睡完,任务就算完成
	printf("[%u][%s] ==> job will be done in %d sec...\n",
		(unsigned)pthread_self(), __FUNCTION__, n);

	sleep(n);

	printf("[%u][%s] ==> job done!\n",
		(unsigned)pthread_self(), __FUNCTION__);

	return NULL;
}

void *count_time(void *arg)
{
	int i = 0;
	while(1)
	{
		sleep(1);
		printf("sec: %d\n", ++i);
	}
}

int main(void)
{
	// 本线程用来显示当前流逝的秒数
	// 跟程序逻辑无关
	pthread_t a;
	pthread_create(&a, NULL, count_time, NULL);

	// 1, initialize the pool 申请线程池结构体内存空间
	thread_pool *pool = malloc(sizeof(thread_pool));
	init_pool(pool, 2);
	//2个线程都在条件变量中睡眠

	// 2, throw tasks
	printf("throwing 3 tasks...\n");
	int aa = (rand()%10);
	int b = (rand()%10);
	int c = (rand()%10);
	
	//添加任务
	add_task(pool, mytask, (void *)&aa);
	add_task(pool, mytask, (void *)&b);
	add_task(pool, mytask, (void *)&c);

	// 3, check active threads number
	printf("current thread number: %d\n",
			remove_thread(pool, 0));//2
	sleep(9);

	// 4, throw tasks
	printf("throwing another 6 tasks...\n");
	
	int d,e,f,g,h,j;
	d = (rand()%10);
	e = (rand()%10);
	f = (rand()%10);
	g = (rand()%10);
	h = (rand()%10);
	j = (rand()%10);
	
	add_task(pool, mytask, (void *)&d);
	add_task(pool, mytask, (void *)&e);
	add_task(pool, mytask, (void *)&f);
	add_task(pool, mytask, (void *)&g);
	add_task(pool, mytask, (void *)&h);
	add_task(pool, mytask, (void *)&j);
	
	// 5, add threads
	add_thread(pool, 2);

	sleep(5);

	// 6, remove threads
	printf("remove 3 threads from the pool, "
	       "current thread number: %d\n",
			remove_thread(pool, 3));

	// 7, destroy the pool
	destroy_pool(pool);
	return 0;
}

2、thread_pool.h

#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include <errno.h> //系统全局变量 errno要添加的头文件
#include <pthread.h>

#define MAX_WAITING_TASKS	1000
#define MAX_ACTIVE_THREADS	20  //当前线程池 最多能 支持加载运行的线程数量

struct task
{
	void *(*do_task)(void *arg);
	void *arg;

	struct task *next;
};
//线程池结构体的数据类型设计
typedef struct thread_pool
{
	pthread_mutex_t lock;
	pthread_cond_t  cond;
	bool shutdown;
	struct task *task_list;
	pthread_t *tids;
	unsigned max_waiting_tasks;
	unsigned waiting_tasks;
	unsigned active_threads;
}thread_pool;


bool init_pool(thread_pool *pool, unsigned int threads_number);
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);
int  add_thread(thread_pool *pool, unsigned int additional_threads_number);
int  remove_thread(thread_pool *pool, unsigned int removing_threads_number);
bool destroy_pool(thread_pool *pool);

void *routine(void *arg);


#endif

3、thread_pool.c

#include "thread_pool.h"

void handler(void *arg)
{
	printf("[%u] is ended.\n",
		(unsigned)pthread_self());

	//解锁
	pthread_mutex_unlock((pthread_mutex_t *)arg);
}

void *routine(void *arg)
{
	//1. 先接住线程池的地址
	thread_pool *pool = (thread_pool *)arg;
	struct task *p;

	while(1)
	{
		//2. 线程取消例程函数
		//将来要是有人要取消我,请先把锁解开,然后再响应取消。
		//压栈的线程取消处理例程
		pthread_cleanup_push(handler, (void *)&pool->lock);
		
		//3. 上锁
		pthread_mutex_lock(&pool->lock);
		
		//如果当前线程池没有关闭,并且当前线程池没有任务做
		while(pool->waiting_tasks == 0 && pool->shutdown == false) //pool->shutdown == false 等价于 !pool->shutdown
		{
			//那么就进去条件变量中睡觉。--摸鱼
			pthread_cond_wait(&pool->cond, &pool->lock); //自动解锁
		}
		
		//要是线程池关闭了,或者有任务做,这些线程就会运行到这行代码
		//判断当前线程池是不是关闭了,并且没有等待的任务
		if(pool->waiting_tasks == 0 && pool->shutdown == true)
		{	
			//如果线程池关闭,又没有任务做
			//线程那么就会解锁
			pthread_mutex_unlock(&pool->lock);	
			
			//线程走人
			pthread_exit(NULL); 
		}
		
		//能运行到这里,说明有任务做 而且线程池 还在运行
		//p指向头节点的下一个
		p = pool->task_list->next;
		
		//让首节点的指针域指向p的下一个节点
		pool->task_list->next = p->next;
		
		//当前任务个数-1
		pool->waiting_tasks--;

		//解锁
		pthread_mutex_unlock(&pool->lock);
		
		//删除线程取消例程函数
		pthread_cleanup_pop(0);
		
		//设置线程不能响应取消
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
		
		//执行这个p节点指向的节点的函数  ---相当于 调用  eat(NULL)  playGame(NULL)
		(p->do_task)(p->arg);
		
		//设置线程能响应取消
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

		//释放p对应的空间
		free(p);
	}

	pthread_exit(NULL);
}

bool init_pool(thread_pool *pool, unsigned int threads_number)
{
	//1. 初始化线程池互斥锁
	pthread_mutex_init(&pool->lock, NULL);
	
	//2. 初始化条件变量
	pthread_cond_init(&pool->cond, NULL);

	//3. 初始化标志位为false,代表当前线程池正在运行。
	pool->shutdown = false;
	
	//4. 初始化任务队列的数据首节点---而且这个数据首结点不存储数据
	pool->task_list = malloc(sizeof(struct task));

	//5. 为储存线程ID号申请空间。 ---相当于  pthread_t tids[20]
	pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);

	//第4步与第5步错误判断
	if(pool->task_list == NULL || pool->tids == NULL)
	{
		perror("allocate memory error");
		return false; //初始化线程池失败
	}

	//6. 为线程池任务队列的头节点的指针域赋值NULL
	pool->task_list->next = NULL;
	
	//7. 设置线程池最大任务个数为1000
	pool->max_waiting_tasks = MAX_WAITING_TASKS;
	
	//8. 当前需要处理的任务为0 ---也就是当前任务队列(链表)的 任务结点的个数 ---nodeNumber
	pool->waiting_tasks = 0;
	
	//9. 初始化线程池中线程池的个数
	pool->active_threads = threads_number;

	//10. 创建线程,创建的每个线程ID号存储到tids
	int i;
	for(i=0; i<pool->active_threads; i++)
	{
		if(pthread_create(&((pool->tids)[i]), NULL,routine, (void *)pool) != 0)
		{
			perror("create threads error");
			return false;
		}
	}

	//11. 线程池初始化成功
	return true;
}
//添加任务----到任务队列(链表)中
bool add_task(thread_pool *pool,void *(*do_task)(void *arg), void *arg)
{
	//1. 为新任务的节点申请空间 ---新建一个数据结点 newNode
	struct task *new_task = malloc(sizeof(struct task));
	if(new_task == NULL)
	{
		perror("allocate memory error");
		return false;
	}
	
	//2. 为新节点的数据域与指针域赋值
	new_task->do_task = do_task;  
	new_task->arg = arg; 
	new_task->next = NULL;  
	
	//3.  在添加任务之前,必须先上锁,因为添加任务属于访问临界资源 -> 任务队列
	pthread_mutex_lock(&pool->lock);
	
	//4. 如果当前需要处理的任务个数>=1000
	if(pool->waiting_tasks >= MAX_WAITING_TASKS)
	{
		//解锁
		pthread_mutex_unlock(&pool->lock);
		
		//打印一句话提示一下,太多任务了
		fprintf(stderr, "too many tasks.\n");
		
		//释放掉刚刚准备好的新节点
		free(new_task);

		return false;
	}
	
	//5. 寻找任务队列中的最后一个节点
	struct task *p = pool->task_list;
	while(p->next != NULL)
		p = p->next;
	//从循环中出来时,p肯定是指向最后一个节点
	
	//6. 让最后一个节点的指针域指向新节点
	p->next = new_task;
	
	//7. 当前需要处理的任务+1  ----结点的数量 +1
	pool->waiting_tasks++;

	//8. 添加完毕,就解锁。
	pthread_mutex_unlock(&pool->lock);

	//9. 唤醒条件变量中的一个线程起来做任务
	pthread_cond_signal(&pool->cond);
	
	return true;
}

int add_thread(thread_pool *pool, unsigned additional_threads)
{
	//如果说你想添加0条,则直接返回0。
	if(additional_threads == 0)
		return 0; 

	unsigned total_threads = pool->active_threads + additional_threads;
				//total_threads = 原本 2条 + 再加2条 
			 //total_threads = 原本 2条 + 现在再加20条  //第19条  第 20条 创建失败 i = 20  i=21
						
	int i, actual_increment = 0;
	
	// i = 2 ; i<22 && i<20; i++
	// i = 2 ; i<4 && i<20; i++
	for(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS;  i++) 
	{
		if(pthread_create(&((pool->tids)[i]),NULL, routine, (void *)pool) != 0) //tids[0]  tids[1]  tids[2]
		{
			perror("add threads error");
			if(actual_increment == 0) 
				return -1;

			break;
		}
		actual_increment++;  //真正创建线程的条数
	}

	pool->active_threads += actual_increment; //当前线程池线程个数 = 原来的个数 + 实际创建的个数
	
	return actual_increment; //返回真正创建的个数
}

     
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
	//1. 如果你想删0条,直接返回。
	if(removing_threads == 0)
		return pool->active_threads;  //返回当前剩余的条数

	int remaining_threads = pool->active_threads - removing_threads;
	//            1         =            4         -  3
	//			  -1		=	         4         -  5


	remaining_threads = remaining_threads > 0 ? remaining_threads : 1;

	int i;  
	//现在线程数 有4 条,要移除3条   tids[0] tids[1] tids[2] tids[3]
	// i = 4-1 ; i>1-1; i-- 
	// i = 3; i>0; i-- 
	for(i=pool->active_threads-1; i>remaining_threads-1; i--) //i ==3 i==2 i==1
	{	
		//errno 是一个系统全局变量 专门用来记录错误码 错误码 可以通过 perror 
		errno = pthread_cancel(pool->tids[i]); //取消这些线程
		if(errno != 0)//出错了
			break;
	}
	// i == 0
	
	//i == 4-1 -->3
	if(i == pool->active_threads-1) //删除失败
		return -1;
	else
	{
		pool->active_threads = i+1; //当前实际线程个数
		return i+1; 
	}
}


bool destroy_pool(thread_pool *pool)
{
	//1. 设置线程池关闭标志为真
	pool->shutdown = true; 
	//2、唤醒全部的线程
	pthread_cond_broadcast(&pool->cond);  //目的: 就是让线程退出!
	
	//2. 接合所有的线程
	int i;
	for(i=0; i<pool->active_threads; i++)
	{
		errno = pthread_join(pool->tids[i], NULL);

		if(errno != 0)
		{
			printf("join tids[%d] error: %s\n",
					i, strerror(errno));
		}
	
		else
			printf("[%u] is joined\n", (unsigned)pool->tids[i]);
		
	}

	//3. 释放一些空间
	free(pool->task_list);
	free(pool->tids);
	free(pool);

	return true;
}

4、Makefile

CC = gcc
CFLAGS = -O0 -Wall -g -lpthread

test:main.c thread_pool.c
	$(CC) $^ -o $@ $(CFLAGS)

debug:main.c thread_pool.c
	$(CC) $^ -o $@ $(CFLAGS) -DDEBUG

clean:
	$(RM) .*.sw? test debug *.o

.PHONY:all clean

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值