11月12日笔记并发7_线程池(结构+模板)

1.线程池
    我们都知道,线程是进程的一个执行分支
    操作系统上 以现场为单位进行调度
    =》理论上而言 一个进程拥有的线程越多 其处理效率越高
    但是 实际上 收操作系统,硬件设备等影响
    一个进程创建的线程数量和处理效率并不是成正比(线性关系)

    因此 一般情况下 线程的创建不能 "为所欲为"
    之前的代码 如果创建成千上万个线程同时拷贝
    那么系统就会卡死

    因此为了解决这种BUG 参考C++,引入了线程池的概念
        =》C没有线程池  


2.线程池的原理  生产者与消费者模型
    生产者
        负责产生需要处理的数据
    消费者
        负责消费数据

    有一个任务队列
    生产者负责添加任务
    消费者负责处理任务

    当任务队列中的任务过多的时候
        需要增加线程去处理任务
    当任务队列中的任务不够的时候
        需要减少线程,减少消耗
    


    生产者
        负责则添加任务
        比如 上一个程序中 读到一个普通文件的时候
            负责将普通文件入队

    线程池
        if(队列不为空)
            {
                唤醒一个队列去执行
            }
    消费者
        等待被唤醒
            唤醒了以后 出队节点并处理任务
                去复制入队的普通文件
        
        消费者正常情况下是不会死亡 除非任务队列
        中的任务太少了,才会取消

        void *thrad_fun(void *arg)
        {
            while(1)
            {
                等待任务来了 被唤醒

                处理任务
            }
        }


模板代码cp.c         thread_pool.c        thread_pool.h

        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>
#include <pthread.h>

#define MAX_WAITING_TASKS	1000
#define MAX_ACTIVE_THREADS	20

#define BUFSIZE			100
#define PATHSIZE		100

// "task": 指令的序列
//		"函数内部": 指令封装在函数内.
// "task" -> 一个函数.
// "task" 提交,是不是马上就执行呢?
// 需要保存这个函数,好让后面的线程去执行它
//	"保存函数" -> 函数指针.

// 函数指针有什么用? 保存一个函数的地址,
// 因为现在不调用它,等到后面再去调用它. callback
//   call me back
// callback回调.:回过头来,调用.



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;//指向线程ID的数组,因为我可能会创建多个线程。
	//size = n * sizeof(pthread_t)

	unsigned int max_waiting_tasks;//表示最大的执行的任务数
	unsigned int waiting_tasks; //目前正在链表上的任务数,即待执行任务
	unsigned int active_threads; //正在服役的线程数
}thread_pool;

/*
	func:初始化一个线程池
	param:pool 一个thread_pool的指针 要有指向的空间
	threads_number:你初始要有几个活动的线程
	return 成功返回true 失败返回false
*/
bool init_pool(thread_pool *pool, unsigned int threads_number);
/*
	func:增加任务
	param:pool 一个thread_pool的指针 要有指向的空间
		do_task:你要执行的函数是哪一个
		task:要执行的函数的参数
	return 成功返回true 失败返回false
*/
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);
/*
	func:增加线程 增加消费者数量
	param:pool 一个thread_pool的指针 要有指向的空间
		additional_threads_number 你要添加几个线程
	return 成功返回添加的线程数 失败返回-1
*/
int  add_thread(thread_pool *pool, unsigned int additional_threads_number);
/*
	func:删除线程
	param:pool 一个thread_pool的指针 要有指向的空间
		removing_threads_number 你要删除几个线程
	return 成功返回取消的线程数 失败返回-1
	/~~~成功返回剩余的线程数量
*/
int  remove_thread(thread_pool *pool, unsigned int removing_threads_number);
/*
	func:销毁线程池
	param:pool 一个thread_pool的指针 要有指向的空间
*/
bool destroy_pool(thread_pool *pool);

void *routine(void *arg);//任务执行函数


#endif

        thread_pool.c

#include "thread_pool.h"

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

void *routine(void *arg)//任务执行函数
{
	#ifdef DEBUG
	printf("[%u] is started.\n",
		(unsigned)pthread_self());
	#endif

	thread_pool *pool = (thread_pool *)arg;//参数其实是个线程池头结点
	struct task *p;//指向我要执行的任务

	while(1)
	{
		/*
		** push a cleanup function handler(), make sure that
		** the calling thread will release the mutex properly
		** even if it is cancelled during holding the mutex.
		**
		** NOTE:
		** pthread_cleanup_push() is a macro which includes a
		** loop in it, so if the specified field of codes that 
		** paired within pthread_cleanup_push() and pthread_
		** cleanup_pop() use 'break' may NOT break out of the
		** truely loop but break out of these two macros.
		** see line 56 below.
		*/
		//================================================//
		//pthread_cleanup_push 和 pthread_cleanup_pop是配套使用的
		//从push开始到pop之间  只要线程中断 不管什么原因 都会去执行push注册了的函数
		//这样做的意思是防止线程意外终止 导致死锁
		pthread_cleanup_push(handler, (void *)&pool->lock);//注册一个线程清理函数,防止死锁
		
		上锁
		pthread_mutex_lock(&pool->lock);
		//================================================//

		// 1, no task, and is NOT shutting down, then wait
		while(pool->waiting_tasks == 0 && !pool->shutdown)
		{
			条件变量进行等待
			pthread_cond_wait(&pool->cond, &pool->lock);//没任务但线程池没有关的时候会阻塞在这里,唤醒的地方有两个
														//一个是在添加任务时,另一个是在销毁线程池时
		}

		// 2, no task, and is shutting down, then exit
		if(pool->waiting_tasks == 0 && pool->shutdown == true)
		{
			pthread_mutex_unlock(&pool->lock);
			pthread_exit(NULL); // CANNOT use 'break';
		}

		// 3, have some task, then consume it
		
		执行的任务task p
		p = pool->task_list->next;//第一个任务结点并没有赋值,所以可以跳过它
		pool->task_list->next = p->next;//更新pool->task_list->next
		pool->waiting_tasks--;
		上面是任务节点task的取出

		//================================================//
		pthread_mutex_unlock(&pool->lock);
		pthread_cleanup_pop(0);//退出带锁结束的保护
		//================================================//

		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); //
		执行线程函数
		(p->do_task)(p->arg);//执行任务
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
		????为什么要修改属性,从默认到可以取消到不可以取消,改的是哪一个线程的属性
        修改是目前的线程的属性,不是执行任务这个线程,防止执行任务时,线程池这个线程被取消

		p->next = NULL;
		free(p);//释放掉以被执行的任务 结点的空间
	}

	pthread_exit(NULL);
}

/*
	init_pool:初始化一个线程池
	@pool: 指针。指向要初始化的纯种池
	@threads_number:线程里常驻线程数
*/
bool init_pool(thread_pool *pool, unsigned int threads_number)//初始化线程池链表,并一个创建线程来运行线程池管理函数
{
	初始化一个线程互斥锁
	pthread_mutex_init(&pool->lock, NULL);
	pthread_cond_init(&pool->cond, NULL);

	pool->shutdown = false;//开机
	//创建第一个任务结点并用task_list指向它,注意第一个任务结点并没有赋值,why?
	pool->task_list = malloc(sizeof(struct task));
	pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
	????上面的宏定义20是指这个数组长度20吗?可以存放20个ID??
    是,设置这个线程池能存放20个线程的ID

	if(pool->task_list == NULL || pool->tids == NULL)
	{
		perror("allocate memory error");
		return false;
	}

	pool->task_list->next = NULL;

	pool->max_waiting_tasks = MAX_WAITING_TASKS;
	pool->waiting_tasks = 0;
	
	 unsigned int threads_number 初始要有几个活动的线程
	pool->active_threads = threads_number;

	int i;
	//创建active_threads个线程来运行任务执行函数
	for(i=0; i < pool->active_threads; i++)   
	{
		线程id的存入
		if(pthread_create(&((pool->tids)[i]), NULL,
					routine, (void *)pool) != 0)
		{
			perror("create threads error");
			return false;
		}
		//条件编译  测试
		#ifdef DEBUG
		printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
			(unsigned)pthread_self(), __FUNCTION__,
			i, (unsigned)pool->tids[i]);
		#endif
	}

	return true;
}

bool add_task(thread_pool *pool,void *(*do_task)(void *arg), void *arg)//添加任务
{
	struct task *new_task = malloc(sizeof(struct task));//创建一个新的任务结点
	if(new_task == NULL)
	{
		perror("allocate memory error");
		return false;
	}
	new_task->do_task = do_task;  //给新的任务结点赋值
	new_task->arg = arg;
	new_task->next = NULL;

	//============ LOCK =============//
	pthread_mutex_lock(&pool->lock);
	//===============================//

	if(pool->waiting_tasks >= MAX_WAITING_TASKS)//如果等待任务数量达到上限
	{
		pthread_mutex_unlock(&pool->lock);

		fprintf(stderr, "too many tasks.\n");
		free(new_task);

		return false;//放弃添加,结束函数
	}
	
	struct task *tmp = pool->task_list;
	while(tmp->next != NULL)
	{
		tmp = tmp->next;//找到最后一个结点
	}

	tmp->next = new_task;
	pool->waiting_tasks++;

	//=========== UNLOCK ============//
	pthread_mutex_unlock(&pool->lock);
	//===============================//

	#ifdef DEBUG
	printf("[%u][%s] ==> a new task has been added.\n",
		(unsigned)pthread_self(), __FUNCTION__);
	#endif

	????唤醒条件变量的作用是什么?提醒开始工作?
    唤醒线程,系统会自动分配一个被唤醒线程进行工作,不需要指定线程工作
	pthread_cond_signal(&pool->cond);//唤醒条件变量
	return true;
}

int add_thread(thread_pool *pool, unsigned additional_threads)//创建活动线程
{
	if(additional_threads == 0)
	{
		return 0;
	}
	//最终你有几个活动的线程
	unsigned total_threads = pool->active_threads + additional_threads;

	int i, actual_increment = 0;
	for(i = pool->active_threads;
	    i < total_threads && i < MAX_ACTIVE_THREADS;
	    i++)	
	{
		线程id存入
		if(pthread_create(&((pool->tids)[i]),
					NULL, routine, (void *)pool) != 0)//创建线程
		{
			perror("add threads error");

			// no threads has been created, return fail
			if(actual_increment == 0)
				return -1;

			break;
		}
		actual_increment++; 

		#ifdef DEBUG
		printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
			(unsigned)pthread_self(), __FUNCTION__,
			i, (unsigned)pool->tids[i]);
		#endif
	}

	pool->active_threads += actual_increment;
	return actual_increment;
}

int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
	if(removing_threads == 0)
		return pool->active_threads;

	int remaining_threads = pool->active_threads - removing_threads;//删除后剩余的线程
	
	remaining_threads = remaining_threads > 0 ? remaining_threads : 1;//为什么要留1,why?线程池本身就是一个线程

	int i;
	for(i = pool->active_threads - 1; i > remaining_threads - 1; i--)//我们的目的就缓解硬件资源紧张而不是结束任务
	{
		//取消线程
		errno = pthread_cancel(pool->tids[i]);

		if(errno != 0)
			break;

		#ifdef DEBUG
		printf("[%u]:[%s] ==> cancelling tids[%d]: [%u]...\n",
			(unsigned)pthread_self(), __FUNCTION__,
			i, (unsigned)pool->tids[i]);
		#endif
	}

	if(i == pool->active_threads-1)
	{	
		从开始就取消线程失败
		return -1;
	}
	else
	{
		输出目前剩余的线程数
		pool->active_threads = i+1;
		return i+1;
	}
}

bool destroy_pool(thread_pool *pool)
{
	// 1, activate all threads
	pool->shutdown = true;
	pthread_cond_broadcast(&pool->cond);
	上面的播报是什么作用?唤醒所有线程?怎么唤醒?对互斥锁有没有需求,为什么没有?自带?
    自带的函数,会唤醒线程池里面所有的线程
	// 2, wait for their exiting
	int i;
	for(i=0; i < pool->active_threads; i++)
	{
		循环结束线程  这个线程id是什么时候存进去的?
        初始化一个线程池的时候,和增加线程数量的时候
		errno = pthread_join(pool->tids[i], NULL);
		if(errno != 0)
		{
			printf("join tids[%d] error: %s\n",
					i, strerror(errno));
		}
	}

	// 3, free memories
	free(pool->task_list);
	free(pool->tids);
	free(pool);

	return true;
}

本线程池可以增加到20个线程,目前里面初始化了10个线程,可以进行的任务数量是1000个

        cp.c   (简单的运用此线程池)

#include <sys/types.h>
#include <dirent.h>

#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <libgen.h>
#include <pthread.h>


#include "thread_pool.h"

	// char *dirname(char *path);

	// char *basename(char *path);



//指向一个"线程池"
thread_pool *pool = NULL;



//拷贝普通文件
struct cp_files
{	
	char file1[512];
	char file2[512];
	
};


执行线程的函数   参数是结构体cp
void* cp_files_routine(void *data)
//void cp_file(char *file1, char *file2)
{
	char *file1, *file2;
		

	int fd1, fd2;
	int ret;
	char buf[1024];

	/取出结构体里面的两个文件名字
	struct cp_files *p = (struct cp_files *)data;
	file1 = p->file1;
	file2 = p->file2;


	//pthread_detach(pthread_self());

	
	fd1 = open(file1, O_RDONLY);
	if (fd1 == -1)
	{
		printf("%s \n", file1);
		perror("open error");
		//return NULL;
		goto cp_return;
	}

	fd2 = open(file2, O_WRONLY | O_CREAT | O_TRUNC, 0777);
	if (fd2 == -1)
	{
		printf("%s \n", file2);
		perror("open error");

		//close(fd1);
		//return NULL;
		goto cp_return;
	}


	while (1)
	{
		ret 是实际读取的字节数
		ret = read(fd1, buf, 1024);
		if (ret == 0)
		{
			break;
		}
		else if (ret > 0)
		{
			int w = write(fd2, buf, ret);
			if (w != ret)
			{
				
			}
		}
		else
		{
			perror("read error");
			break;
		}
		
	}


cp_return:


	free(data);
	close(fd1);
	close(fd2);

	return NULL;
}


//拷贝目录

void cp_dir(char *path1, char *path2)
{
	struct dirent * dirp = NULL;
	DIR *dir = opendir(path1);
	if (dir == NULL)
	{
		perror("opendir error");
		return ;
	}

	char path2_name[512];
	char *p_name = basename(path1);

	sprintf(path2_name, "%s/%s", path2, p_name);
	
	创建文件2 ,给所有权限
	mkdir(path2_name, 0777);
	path2 = path2_name;

	while (dirp = readdir(dir))
	{
		///?????strcmp比较什么?
        如果读到的文件名字是目录项则继续往下面读,后面递归
		if (strcmp(dirp->d_name, ".") == 0 || 
				strcmp(dirp->d_name, "..") == 0)
		{
			continue;
		}
		
		char filename[512];

		sprintf(filename, "%s/%s", path1, dirp->d_name);
	

		struct stat st;
		lstat(filename, &st);

		/普通文件  ||或  符号链接文件(快捷方式)
		if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
		{

			char file[512];
			sprintf(file, "%s/%s", path2, dirp->d_name);
			struct cp_files*  cp = malloc(sizeof(*cp));

			复制filename到cp->file1   复制到结构体cp里面
			strcpy(cp->file1, filename);
			strcpy(cp->file2, file);
			
			//cp_file( filename, file);

			给链表节点增加任务
			add_task(pool, cp_files_routine, cp);
		
		}

		为 目录项
		else if (S_ISDIR(st.st_mode))
		{
			char pathname[512];
			sprintf(pathname, "%s/%s", path2, dirp->d_name);

			mkdir(pathname, 0777);
			
			递归函数 递归本身
			cp_dir(filename,  pathname);
			
		}
		
		
	}


	closedir(dir);

	return ;

}

// cp f/d1  f/d2
int main(int argc, char *argv[])
{
	int ret;
	struct stat st1, st2;

	if(	stat(argv[1], &st1) || stat(argv[2], &st2) )
	{
		printf("-----\n");
		perror("stat error");
		return ;
	}

	if (S_ISREG(st1.st_mode) && S_ISREG(st2.st_mode))
	{
		//cp_file(argv[1], argv[2]);
	}

	//定义一个"线程池"并初始化它
	 pool = malloc(sizeof(*pool));

	初始化线程池 
	init_pool(pool, 10);
	
	if (S_ISDIR(st1.st_mode) && S_ISDIR(st2.st_mode))
	{
		/cp目录中的文件	
		cp_dir(argv[1], argv[2]);
	}

	销毁线程池
	destroy_pool( pool);
	
	return 0;
}


                                                                                                                                                           

线程池函数结构

总体:

        两个结构体,六个函数


结构体: 任务节点

                        函数指针  线程函数   do_task

                        一个指针  上面函数的参数的传入    arg 

                        指向下一个任务节点


结构体: 线程池头结点

                        上锁  保护线程池   lock

                        条件 用于唤醒线程  cond

                        退出线程池使用标志 shutdown

                        任务节点组成的链表  task_list

                        存放线程ID的数组   tids

        线程池容量:

                        最大进行的任务数  max_waiting_tasks

                        正在链表上的任务数量   waiting_tasks

                        正在运行的线程数  active_threads   (线程ID的数组中的数量)


函数:(以下函数的参数部分选自上面的模板)

        初始化 线程池  init_pool(pool ,10);

        增加任务数      add_task(pool,cp_files_routine,cp);   【1.线程池 2.线程函数 3.参数】

        增加线程         add_thread(pool,10);

        删除线程         remove_thread(pool,2);

        销毁线程池     destroy_pool(pool);

        任务执行函数  routine();

各个函数的具体结构:

init_pool(pool,10);

        初始化结构线程池:

                上锁,条件变量,

        创建一个线程执行函数:

        .......

add_task(pool,cp_files_routine,cp);

        初始化 new_task

        上锁  线程池 

        判断任务数量---> temp :task_list的尾结点   new_task->增加上去

        解锁  线程池

        唤醒条件变量(自动分配线程执行任务task)

add_thread(pool,10);

        安全判断 

        最终活动的线程

        从新的线程数增加创建新线程

        修改活动线程数量

remove_thread(pool,2);

        安全判断

        最终活动的线程数

        安全监测活动线程数量

        从最后一个开始取消相应的线程

destrey_pool(pool);

        修改是否退出标志

        播报,唤醒所有线程

        按照ID依次结束线程for循环

        .....

routine();

        上锁 判断情况:

                1.等待使用线程池

                2.退出使用线程池

                3.执行使用线程池: 取出任务节点:task_list--;  ---->解锁

                                                 执行线程函数

                .....



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值