线程池的创建

为什么需要线程池(thread pool)呢?

        我们知道应用程序创建一个对象,然后销毁对象是很耗费资源的。创建线程,销毁线程,也是如此。因此,我们就预先生成一些有限数量线程,另一方面当有任务做的时候把任务挂队列上。
        线程:不停的去看有没有还未完成的任务。

有:就去完成任务;没有:休息,等通知。

1.整体框架的搭建,即主进程(main)要做的事情

1.1 初始化一个线程池

    bool init_pool(thread_pool *pool, unsigned int threads_num);
    thread_pool:自定义的一个描述池子的数据结构;
    初始化threads_num的干活的线程,这些线程都做同样的事情:取任务,完成任务;
    初始化一些必要的用于管理池子的必要资源:锁、条件变量。

1.2 添加任务

    bool add_task(thread_pool *pool, void *(*do_task)(void *arg), (void *)arg);
    将任务do_task挂到一个任务队列中去,并且通知线程。 

1.3 销毁线程池

    bool destroy_pool(thread_pool *pool);
    通知干活的线程可以退出了,通过设置一个标志,如shutdown;
    等待干活的线程回来;
    销毁一些资源:锁,条件变量。

接下来就细分每一步操作了

·······························································································································································

同时有一些值得注意的点:

(1)条件变量pool->cond:在线程的routine函数中,某些情况下,线程会进入到休息状态,需要等待某个条件出来唤醒它。
谁来唤醒它:
        父进程退出---------->destroy_pool,并记得唤醒所有线程,并使用 pthread_con_broadcast。
        来了新任务 --------->add_task,唤醒一个等在cond的线程上 pthread_cond_signal。                                                     

(2)互斥锁pool->lock:保护pool->waiting_tasks,pool->task_list。
        至于怎么判别需要保护:公共的变量可能存在并发执行的情况时需要。
        我们的项目中,需要加锁的函数涉及:routine, add_task。
        在routine这个函数中,线程是有可能退出(调用了pthread_exit),产生带锁退出的情况。
那么,解决方法:
       线程在pthread_exit前记得把锁解开;
       或者,注册一个清理函数,pthread_cleanup_push必须与pthread_cleanup_pop成对使用。

·······························································································································································

2.分步解析

2.1 线程应该做的事情

其实就是考虑pthread_create函数的第三个参数该怎么写,假设我们对这个函数取名routine。若干个任务怎么去描述?

可以通过组建任务队列,即链表。要做什么事情,就把要做的动作定义在一个函数里头,传给函数(add_task)里的do_task。(thread_pool结构体的定义放在2.2中)

void handler(void *arg)
{
	pthread_mutex_unlock((pthread_mutex_t *)arg);//解锁
}


void *routine(void *arg)//任务执行函数
{
	struct task * ptask;
	thread_pool *pool = (thread_pool *)arg; //线程池指针通过参数传进来
	while(1)
	{
		//--------LOCK--------------//
		pthread_cleanup_push(handler, (void *)&pool->lock);
		pthread_mutex_lock(&pool->lock);
		//---------------------------//
		
		//没事做,休息,等消息  ---->条件变量
		while( (0 == pool->waiting_tasks) && (pool->shutdown != 1))
		{
			pthread_cond_wait(&pool->cond, &pool->lock);
		}
		//没事做,父进程要撤,所有线程必须撤
		if( (0 == pool->waiting_tasks) && ( 1 == pool->shutdown) )
		{
			pthread_exit(NULL);
		}
		//有事做,赶紧去取链表上的任务
		ptask = pool->task_list;
		pool->task_list = ptask->next;
		pool->waiting_tasks--;
		//--------UNLOCK--------------//
			pthread_mutex_unlock(&pool->lock);
			pthread_cleanup_pop(0);
		//---------------------------//

		(ptask->do_task)(ptask->arg);
		free(ptask);
	}
}

        然后我们开始搭建池子结构,这时可以把它定义为一个结构体。至于为什么要设置一个池子,当然是因为控制任务的数量不能太多,不能一次涌入太多的任务。

2.2 池子结构体(thread_pool)的定义

        定义一个常量MAX_WAITING_TASK:表示能接纳的最大等待任务数;
        在pool结构中需要一个分量waiting_tasks:来记录当前正在等待的任务数;
        在pool结构中还需要一个分量active_threads:来记录当前到底有几个线程在干活;
        显然,任务列表task_list是一个很重要的公共资源,父进程和干活的线程都要访问;
        为了保护这个重要的人物:task_list;则需要用互斥锁+条件变量。

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;  //是否退出。
	unsigned waiting_tasks; //目前等待执行的任务
	unsigned active_threads; //干活的线程数
	struct task *task_list;//任务链表,即指向第一个任务结点
	pthread_t *tids;//指向线程ID的数组,因为可能会创建多个线程。
}thread_pool;

        定义好之后我们就需要初始化池子。

2.3 初始化池子(init_pool)

/* 功能:初始化一个线程池
 * 输入: 
 *		pool:线程池指针
 *		threads_number:线程池中活跃线程的个数
 * 返回:成功 true, 失败 false
 */
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
	pthread_mutex_init(&pool->lock, NULL);
	pthread_cond_init(&pool->cond, NULL);
	pool->shutdown = 0;
	pool->waiting_tasks = 0;
	pool->active_threads = threads_number;
	pool->task_list = NULL;
	pool->tids = malloc(sizeof(pthread_t) * pool->active_threads);
	if( NULL == pool->tids)
	{
		perror("pool malloc err");
		return false;
	}
	int i, r;
	for(i=0; i<pool->active_threads; i++)
	{
		r = pthread_create(&pool->tids[i],
						NULL,
						routine,
						(void *)pool);//将线程池指针作为参数传给routine,
						//routine里面要用pool的信息
		if( -1 == r)
		{
			perror("pthread err");
			return false;
		}
	}
	return true;
}

2.4 添加任务到池子中(add_task)

         由于池子已经初始化完毕,我们就可以往里面添加任务了。

/* 功能:将任务加到线程池的任务队列中
 * 输入: 
 *		pool:线程池指针
 *		do_task:新任务的函数指针
 *		arg:传递给do_task函数的参数
 * 返回:成功 true, 失败 false
 */
#define MAX_WAITING_TASKS	1000
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg)
{
	struct task *new_task = (struct task*) malloc(sizeof(struct task));
	new_task->do_task = do_task;
	new_task->arg = arg;
	new_task->next = NULL;

	if(pool->waiting_tasks >= MAX_WAITING_TASKS )
	{
		printf("排队的人都太多了,拒绝入园...\n");
		free(new_task);
		return false;
	}
	//--------LOCK--------------//
	pthread_mutex_lock(&pool->lock);
	//---------------------------//
	//这里采用尾插法,先来的任务先插入
	struct task *p;
	p = pool->task_list;
	if( NULL == p)
	{
		pool->task_list = new_task;
	}
	else
	{
		while( p->next!=NULL )
		{
			p = p->next;
		}
		p->next = new_task;		
	}
	pool->waiting_tasks ++;
	//--------UNLOCK--------------//
		pthread_mutex_unlock(&pool->lock);
	//---------------------------//
	pthread_cond_signal(&pool->cond);
	return true;
}

2.5 销毁线程池(destroy_pool)

bool destroy_pool(thread_pool *pool)
{
	int i;
	pool->shutdown = 1;
	pthread_cond_broadcast(&pool->cond);
	
	for( i = 0; i<pool->active_threads; i++)
	{
		pthread_join( pool->tids[i] , NULL);
		//free(pool->tids[i]);
	}
	pthread_cond_destroy(&pool->cond);
	pthread_mutex_destroy(&pool->lock);
	free(pool);
	return true;
}

上述一切完成后我们就可以在main函数(即父进程)里调用线程池了,运用第一大点的所述,步骤大致如下:

<<<<1.初始化线程池   init_pool

<<<<2.加入任务  (自定义)

<<<<3.销毁线程池   destroy_pool

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值