首页 > 编程语言 >系统编程-线程池

系统编程-线程池

时间:2023-09-02 14:24:48浏览次数:36  
标签:task 编程 系统 任务 threads pthread 线程 pool

工程背景

  • 考虑这么一个情况:为应对某场景的实际需求,要在程序中创建大量线程,并且这些线程的数量和生命周期均不确定,可能方生方死,也可能常驻内存,如何在满足其要求的同时,尽可能降低系统负载?
    一个基本事实是,线程的创建和销毁都是需要额外的系统资源的,如果线程的生命周期很短,那么相对于实际干活的时间,来回重复创建和销毁就显得很不划算,但也不能让线程执行完任务之后耗着不走,因此就需要一个比较科学合理的布局,来管控线程,一个比较成熟的方案是:在上述情况下,将线程放入一个类似缓冲区的池子中,对于生命周期很短的任务,那么什么时候需要线程就从池子中捞出来干活,干完活就放回池子且让线程保持整装待命,并通过精巧的设计使得池子中的线程数量可以动态地发生变化,让线程既可以应对并发性需求,又不会浪费系统资源,这种设计思路就是线程池。
    线程池的基本想法,是将许多线程,放置一个池子中(实际就一个结构体),只要有任务,就将任务投入池中,这些线程们通过某些机制,及时处理这些任务。为了便于管理,线程池还应当提供诸如初始化线程池、增删线程数量、检测未完成任务的数量、检测正在执行任务的线程的数量、销毁线程池等等基本操作。

逻辑框架

  • 要让线程池好用,主要是解决如下两个问题:
    如何组织线程?
    如何组织任务?
    image
    image
    image

线程组织

  • 从上图可知,线程被创建出来之后,都是处于睡眠状态,它们实际上是进入到了条件变量的等待队列中,而任务都被放进一个链表中,被互斥锁保护起来

  • 下面是线程池整个的一生

    1. 创建
    2. 写遗书(准备好退出处理函数,放了防止在持有一把锁的状态中(执行任务)被杀死)
    3. 试图持有互斥锁(等待任务)
    4. 判断是否有任务,若无则进入条件变量中等待任务,若有就进入第五步
    5. 从任务链表中取一个任务
    6. 释放互斥锁
    7. 销毁遗书(将备用的退出处理函数弹栈,避免占内存)
    8. 执行任务,完毕之后就重回第二步
  • 线程池代码案例:

    一、分析线程池文件
    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;
    }
    
    总结:初始化线程池函数其实就是给线程池的结构体赋值,将一些变量赋初值,以及初始化变量,为指针申请空间。
    
    2)如何添加任务呢?  ->  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;
    }
    
    总结: 插入一个新节点到任务队列的末尾,唤醒条件变量中的一个线程起来做任务。
    
    3)线程例程函数  -> 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);
    }
    
    总结:线程去任务队列中拿一个节点出来,就执行该任务(过程不能被取消),如果没有任务做,就去睡觉。
    
    4)添加线程  -> 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条。
    
    5)删除线程  -> 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条。
    
    6)销毁线程池。  -> 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;
    }
    
    总结: 唤醒所有的小孩,让小孩走人。
    

标签:task,编程,系统,任务,threads,pthread,线程,pool
From: https://www.cnblogs.com/bcc0729/p/17673628.html

相关文章

  • 银澎云计算 好视通视频会议系统 任意文件下载 CNVD-2020-62437
    漏洞描述银澎云计算好视通视频会议系统存在任意文件下载,攻击者可以通过漏洞获取敏感信息漏洞复现fofa语法:app="好视通-视频会议"登录页面如下:POC:https://xxx.xxx.xxx.xxx/register/toDownload.do?fileName=../../../../../../../../../../../../../../windows/win.inin......
  • 基于Android的校园共享单车租赁管理系统的设计与实现-计算机毕业设计源码+LW文档
    一、选题的目的和意义目的:校园共享单车租赁管理的设计与实现是以解决高校师生课余时间紧张的问题为目的的。在校内设置共享单车,大大减少了师生上课路途所用的时间,从而可以提高学生的学习效率。共享单车操作快捷、方便、经济,对于经济能力较低的大学生而言是一种绝佳的交通工具。相......
  • os --- 多种操作系统接口¶
    os.path ---常用路径操作源代码: Lib/posixpath.py (用于POSIX)和 Lib/ntpath.py (用于Windows)。此模块实现了一些有用的路径名称相关函数。要读取或写入文件请参见 open(),对于访问文件系统请参阅 os 模块。传给path形参的可以是字符串、字节串或者任何实现了......
  • 如何扩容Linux文件系统?看这里↓↓↓
    一、在虚拟机中添加一块新磁盘注意:如果是未关机的状态下,添加完新的虚拟磁盘需要重启才能生效查看当前系统内的磁盘数量,确认我们新添加的磁盘能正常显示了fdisk-l二、为新添加的磁盘创建分区fdisk/dev/sdb命令(输入m获取帮助):n #输入n创建分区分区类型p主分区(0......
  • 8.28-9.3学习总结博客八:数据工程与系统部署
    博客题目:学习总结八:数据工程与系统部署实践内容概要:了解数据工程的基本概念和核心技术,学习如何将学到的技能应用于实际项目中,并了解数据处理系统的设计和部署。学习资源:推荐的数据工程、系统部署和项目实践的教程、实践资源和学习资料。实践内容:通过针对实际项目的数据处理和系统......
  • 基于Java的高校学生请假审批系统的设计与实现-计算机毕业设计源码+LW文档
    一、选题的目的和意义:计算机技术的发展,带来了时代变革,我们的生活方式发生了重大改变。计算机网络的普及使得信息共享成为现实,利用数据库进行信息存储分析,优化了工作方式,提高了工作效率,经过多年的发展,数据库已经应用到社会生活的方方面面,完善的数据库技术和理论基础为计算机软件提......
  • Linux系统之jq工具的基本使用
    (Linux系统之jq工具的基本使用)一、jq工具介绍1.jq工具简介jq是一款轻量级的命令行json处理工具,可以帮助用户轻松处理json格式的数据。它可以从标准输入读取json数据,也可以从文件中读取。同时,它支持各种查询和过滤操作,例如选择、过滤、转换、排序和格式化等。2.jq工具的特......
  • C/C++毕业设计管理系统[2023-09-02]
    C/C++毕业设计管理系统[2023-09-02]二、毕业设计管理系统学校有若干学院,每个学院有若干专业,需要通过一个毕业设计管理系统对现有的毕业设计情况进行管理。系统适用对象:教务处管理员、院系负责人、教师、学生。1、教务处管理员:全校教学事务管理、全校课题过程管理、学生及课题......
  • Django系统报错总结 1
    Django系统报错总结1 本章节,继续总结前面商品系统编写中遇到的报错问题TypeError:Product()gotunexpectedkeywordarguments:'update_time','seller'因为在Product类中没有定义参数update_time和seller。要解决这个问题,你需要确保在Product类中添加这些参数的定义。......
  • 【SpringBoot】面向切面编程AOP
    一.基本概念oop:主要以函数或者过程为程序的基本组织方式,系统功能是由一组相关的过程和函数序列构成,主要实现通过继承,多态和封装aop:这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。简单来讲就是方法和方法的关联,将公用代码从核心代码中抽......