线程池的概念及实现(文件夹的拷贝)

线程池的概念及实现(文件夹的拷贝)


一、普通的线程使用

使用线程的方式复制文件(任务)可能会产生一些问题:

  1. copy文件的时候,本质上还是"串行"的
    开一个线程去复制文件的时候,主线程会因为pthread_join而阻塞
    如果不使用pthread_join去等待子线程结束
    有可能会任务线程被强制退出(进程结束了,但是复制文件的线程还没有结束)
    导致有一部分文件可能复制不完全

  2. 也可能导致资源浪费(线程分离)

    可能创建的线程太多了(发现一个文件就会创建一个线程)

    线程池的模式(是一种解决问题的思路/方案)

二、线程池

线程池(pthread_pool)
是一种多线程的处理方式,处理方法是将"生产者"线程提出的任务添加到"任务队列",创建一些线程自动的去完成"任务队列"上面的任务。

​ 通常情况下,多线程编程都是在需要完成任务的时候,创建一个新线程,然后让新线程去完成指定的任务(函数)。完成之后,自动退出,在一般情况下,是可以满足我们的需求的

​ 但是当我们需要创建大量的线程,并且线程都是去执行一个非常简单的任务,然后线程自动销毁的情况,上面的模式就不适用了
​ 如:

		大文件夹的copy
        WEB服务器的响应
        email邮件服务器
        数据库的操作
   我们的程序需要面对大量的请求,同时这些请求的任务有非常的简单
    完成任务相对于整个过程(创建,执行函数,销毁线程)来说,占用的时间非常少
    程序有可能会一直处于创建线程和销毁线程的状态(大部分的CPU时间都浪费在创建线程和销毁线程)

​ 线程池就是解决这一类问题的一个方案,可以降低频繁创建和销毁线程带来的实际的开销

线程池的技术思路如下:

一般采用预创建线程技术,也就是说,在启动应用程序的时候,就创建一系列的线程
线程固定的执行一个"任务分配函数",当"任务队列"没有任务的时候,让线程自动的休眠(等待一个条件变量)
当"任务队列"有任务的时候,线程会被唤醒,去执行任务,线程完成任务的时候,不会被销毁,而是自动的执行任务队列的下一个任务.…而且当任务比较多的时候,可以有函数接口去增加线程的数量,当任务比较少的时候,你可以有函数接口去减少线程数量.

创建和销毁线程的时间再整个任务过程中的时间占比很小的时候,没有必要使用线程池.

三、实现(文件夹的拷贝)

首先,线程池的内部应该有什么呢?

typedef struct pthread_pool{
//互斥锁,用来保护任务队列(共享资源)
pthread_mutex_t mutex;
//条件变量,表示任务队列是否存在任务
pthread_cond_t cond;
//指向所有线程ID的数组,保存线程池中所有的线程ID
pthread_t *tid;
//任务队列(链表),指向第一个需要完成的任务
struct task *task_list;
//当前任务队列中的数量
unsigned int cur_tasks;
//当前线程数量
unsigned int active_threads;
//线程池的最大容量
unsigned int max_threads;
//表示线程池是否退出
bool shutdown;}

其次,任务队列中呢?

typedef struct task
{
	//数据域
	void *(*my_task)(void *arg);	//函数地址指针,指向你要完成的任务(cp_file)
	void *arg;	//函数的参数指针,指向任务函数的参数
	//指针域
	struct task*next;	//下一个任务的地址
}Task;

最后,线程池需要对外提供什么接口呢?

pthread_pool.h

/*
	init_pool:线程初始化函数,可以指定初始化的线程
	@pool:指针,指向你要初始化的线程池
	@thread_num:初始时,线程池中拥有的线程池
	返回值:成功:true;失败:false
*/
bool init_pool(pthread_pool *pool,unsigned int thread_num);

/*
	destroy_pool:线程池销毁函数,销毁指定的线程池
	@pool:指向需要销毁的线程池
*/
void destroy_pool(pthread_pool *pool);

/*
	my_routine:任务分配函数,所有的线程在开始的时候都执行这个函数
	此函数会不断的从线程池的任务队列中取下任务结点,去执行任务节点中包含的任务
	@arg:线程池的地址(可以通过这个地址在函数内访问线程池)
*/
void *my_routine(void *arg);

/*
	add_task:向线程池的任务队列添加任务
	@pool:需要添加任务的线程池
	@my_task:函数指针,表示当前任务节点需要完成的任务
	@arg:指针,执行任务函数的参数
	返回值:true:成功;false:失败
*/
bool add_task(pthread_pool *pool,void *(*my_task)(void *arg),void *arg);

/*
	add_thread:增加线程池中服役线程的数量
	@pool:指针,指向需要添加线程的线程池
	@add_num:需要添加的线程数量
	返回值:返回实际增加的线程数量
*/
int add_thread(pthread_pool *pool,unsigned int add_num);

/*
	remove_thread:减少线程池中的线程数量(不是销毁线程池)
	@pool:需要减少的哪一个线程池的数量
	@remove_num:你要减少的线程数量
	返回值:最终剩下的线程数量
*/
int remove_thread(pthread_pool *pool,unsigned int remove_num)

pthread.c

/*
	init_pool:线程初始化韩式,可以初始化指定的线程池
	@pool:指针,指向你需要初始化的线程池
	@thread_num:初始时,你的线程池中拥有多少个线程
	返回值:
		成功:true
		失败:false
*/
bool init_pool(pthread_pool *pool,unsigned int thread_num)
{
    //初始化线程池结构体中的变量
    pool->cur_tasks = 0;
    pool->active_threads = thread_num;
    pool->max_pthreads = MAX_NUM;
    //是否销毁线程池
    pool->shutdown = false;
    //初始化线程互斥锁和条件变量
    pthread_mutex_init(&(pool->mutex),NULL);	//初始化互斥锁
    pthread_cond_init(&(poool->cond),NULL);		//初始化条件变量
    //创建任务链表
    pool->task_list = (struct task*)malloc(sizeof(struct task));
    pool->task_list->next = NULL;
    //创建固定数量的线程,让所有的线程去执行“任务分配函数”
    pool->tid = (pthread_t *)malloc(thread_num*sizeof(pthread_t));
    //判断分配空间是否失败
    if(pool->task_list == NULL||pool->tid == NULL)
    {
        perror("allocate memory error.\n");
        return false;
    }
    int i = 0;
    //创建线程
    for(i = 0;i < pool->active_threads;i++)
    {
        if(pthread_create(pool->tid+i),NULL,my_routine,(void *)pool != 0)
        {
            char errm[1024] = {0};
            sprintf(errm,1024,"create %d pthread error:%s",i,strerror(errno));
            fprintf(stderr,"%s",errm);
            return false;
        }
        //条件编译(调试开关)
#ifdef DEBUG
        printf("[%s]===>tids[%d]:[%u] is create!\n",__FUNCTION__,i,(unsigned int)pool->tid[i]);
#/*  
my_routine:任务分配函数,所有的线程在开始的时候都执行这个函数
这个函数会不断的从线程池的任务队列在取下任务节点,去执行任务节点中包含的任务(函数指针+参数)
@arg:线程池的地址(可以通过这个地址在函数内访问线程池)
*/
    }
    return true;
}
/*
destory_pool:线程池销毁函数,销毁指定的线程池
@pool:指向你要销毁的线程池
*/
void destroy_pool(pthread_pool *pool)
{
    //先上锁,防止因条件变量发生改变线程阻塞
    pthread_mutex_lock(&pool->mutex);
    //唤醒所有等待的线程
    //shutdown方法是启动线程池有序关闭的方法,它在完全关闭之前会执行完之前所有已经提交的任务,并且不会再接受任何新任务
    pool->shutdown = true;
    pthread_cond_broadcast(&(pool->cond));
    pthread_mutex_unlock(&pool->mutex);
    //利用join函数回收每一个线程
    int i = 0;
    for(i = 0;i < pool->active_threads;i++)
    {
        errno = pthread_join(pool->tid[i],NULL);
        if(errno != 0)
        {
            printf("join tid[%d] failed:%s\n",i,strerror(errno));
        }
#ifdef DEBUG
        printf("join tid[%d]:[%u]success!\n",i,(unsigned int)pool->tid[i]);
#endif
    }
    //主线程等待子线程的终止。也就是在子线程调用了pthread_join()方法后面的代码,只有等到了子线程结束了才能执行。
    //释放所有为线程池分配的空间
    free(pool->tid);
    //销毁互斥锁和条件变量
    pthread_cond_destroy(&(pool->cond));
    pthread_mutex_destroy(&pool->mutex);
    //销毁等待队列
    free(pool->task_list);
}

/*  
my_routine:任务分配函数,所有的线程在开始的时候都执行这个函数
这个函数会不断的从线程池的任务队列在取下任务节点,去执行任务节点中包含的任务(函数指针+参数)
@arg:线程池的地址(可以通过这个地址在函数内访问线程池)
*/
void *my_routine(void *arg)
{
    //调试开关(条件编译)
#ifdef DEBUG
    printf("[%u] is start!\n",(unsigned int)pthread_self());
#endif
    //arg表示线程池的指针
    pthread_pool *pool = (pthread_pool *)arg;
    //指向取下来的任务节点
    struct task *p = NULL;
    while(1)
    {
        //============================//
        //注册线程清理函数,获取互斥锁,防止线程带锁退出
        //当线程在Push-pop之间的操作中异常退出才会调用my_handler函数
        //p操作
        pthread_cleanup_push(my_handler,(void *)&pool->mutex)
        pthread_mutex_lock(&pool->mutex);
        //===========================//
        //取下任务节点,分情况讨论
        //a.没有任务且线程池不需要退出
        while(pool->cur_tasks == 0 && !pool->shutdown)
        {
            //线程休眠
            //在增加任务和线程池销毁时唤醒,pthread_cond_wait () 用于阻塞当前线程,等待别的线程使用pthread_cond_signal ()或pthread_cond_broadcast来唤醒它。
            pthread_cond_wait(&pool->cond,&pool->mutex);
        }
        //用while醒来再判断一遍,防止虚假唤醒
        //b.没有任务且线程池退出
        if(pool->cur_tasks == 0 && pool->shutdown)
        {
            //线程退出
            pthread_mutex_unlock(&pool->mutex);
            //线程执行过程中,遇到 pthread_exit() 函数结束执行
            //防止带锁退出
            pthread_exit(NULL);
        }
        //c.有任务且线程池没有退出
        p = pool->task_list->next;
        //在初始化的时候,第一个任务节点是空的
        pool->task_list->next = p->next;
        p->next = NULL;
        pool->cur_tasks--;
        printf("取下任务节点!\n");
        //================================//
        //释放线程互斥锁
        pthread_mutex_unlock(&pool->mutex);
        //释放线程清理函数
        pthread_cleanup_pop(0);
        //通过任务节点的指针去执行任务
        //先设置不可删除属性,防止执行任务过程中线程被删除
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
        printf("555\n");
		(p->my_task)(p->arg);
		printf("666\n");
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
		//释放任务节点
		free(p);
		p = NULL;
    }


/*
add_task:向线程池的任务队列添加任务
@pool:你要添加的任务的线程池
@my_task:函数指针,表示当前任务节点需要完成的任务
@arg:指针,执行任务函数的参数
返回值:
	true:表示添加成功
	false:表示添加失败
*/

bool add_task(pthread_pool *pool,void *(*my_task)(void *arg),void *arg)
{
	printf("成功添加任务!\n");
	//分配一个新节点
	Task *new_task = (struct task*)malloc(sizeof(struct task));
	 if(new_task == NULL)
    {
        perror("allocate memory failed!");
        return false;
    }

	//给新节点赋值
	new_task->my_task = my_task;
	new_task->arg = arg;
	new_task->next = NULL;
	//获取线程互斥锁
	//p
	pthread_mutex_lock(&pool->mutex);
	//加入任务队列(链表)
	struct task *tmp = pool->task_list;
    while(tmp->next != NULL)
    {
        tmp = tmp->next;
    } 
	//task_list是没有保存任务结点的
    tmp->next = new_task;
    pool->cur_tasks++;
	//task_list_head是没有保存任务结点的
	//assert(pool->task_list_head != NULL);
	//assert通过检查表达式额的值来决定是否需要终止程序
	//如果表达式的值是假的,它先向标准错误流打印错误信息,再调用abort终止程序运行
	//释放线程互斥锁
	pthread_mutex_unlock(&pool->mutex);
	//唤醒等待条件的线程
	pthread_cond_signal(&pool->cond);

	return true;
}


/*
add_thread:增加线程池中的服役线程数量
@pool:指针,指向你要添加线程的线程池
@add_num:你要添加的线程数量
返回值:
	返回实际增加的线程数量
*/
int add_thread(pthread_pool *pool,unsigned int add_num)
{
	if(add_num == 0)
	{
		return 0;
	}
	unsigned int total_threads = pool->active_threads + add_num;
	int i = 0,n = 0;
	for(i = pool->active_threads;i < total_threads;i++)
	{
		if(pthread_create(pool->tid+i,NULL,my_routine,(void *)pool) != 0)
        {
            char errm[1024] = {0};
            snprintf(errm,1024,"create %d pthread error:%s",i,strerror(errno));
            fprintf(stderr,"%s",errm);
        }else 
        {
            n++;			//增加成功的线程数
#ifdef DEBUG 
        printf("[%s] ====> tids[%d]:[%u] is created!\n",__FUNCTION__,i,(unsigned int )pool->tid[i]);
#endif
        }
	}
	pool->active_threads += n;
	return n;
}


/*
remove_thread:减少线程池中的线程数量(不是销毁线程池)
@pool:你要减少哪一个线程池中的线程数量
@remove_num:你要减少的线程的数量
返回值:
	返回最终剩下的线程数量
*/
int remove_thread(pthread_pool *pool,unsigned int remove_num)
{
	if(remove_num == 0)
	{
		return pool->active_threads;
	}
	unsigned int remaining_threads = pool->active_threads - remove_num;
	//为了缓解硬件资源,并不是说销毁线程池,所以至少保留一个活动线程
    remaining_threads = remaining_threads > 0?remaining_threads:1;
	int i = 0;
	for(i = pool->active_threads - 1;i > remaining_threads - 1;i--)//数组下标从0开始
	{
		errno = pthread_cancel(pool->tid[i]); //取消指定的线程
        if(errno != 0)
        {
            break;
        }
#ifdef DEBUG 
        printf("[%s] ====> tids[%d]:[%u] is cancel!\n",__FUNCTION__,i,(unsigned int )pool->tid[i]);
#endif
    }

    //最终剩下的线程数量
    pool->active_threads = i+1;		//remaining_threads
    return i+1;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include "my_cpdir.h"
#include "pthread_pool.h"

//./main  SRC_DIR  DEST_DIR
//./main 123 456
int main(int argc,char *argv[])
{
	if(argc != 3)
	{
		printf("arg num error\n");
		exit(-1);
	}

	//定义一个线程池
    pthread_pool pool;

    //初始化线程池
    init_pool(&pool,10);
    //添加线程数量
	
    //利用线程池去完成复制目录的任务
    cp_dir(&pool,argv[1],argv[2]);
	printf("444\n");
    //销毁线程池
	destory_pool(&pool);
	return 0;
}

my_cpdir.c

#include"my_cpdir.h"
/*
cp_dir:把src指向的目录复制为dest指向的目录
@pool:把要复制的文件任务添加到pool的任务队列
src:要复制的目录的路径名
dest:目标目录的路径名
*/
void cp_dir(pthread_pool *pool,const char *src,const char *dest)
{
	//获取src和dest的绝对路径
	char abs_cur[PATHSIZE] = {0};//获取当前的工作路径
	getcwd(abs_cur,PATHSIZE);
	
	char abs_src[PATHSIZE] = {0};  //保存源目录的绝对路径
	chdir(src);
	getcwd(abs_src,PATHSIZE);
	
	char abs_dest[PATHSIZE] = {0}; //保存目标目录的绝对路径
	chdir(abs_cur);
	mkdir(dest,0664);
	chdir(dest);
	getcwd(abs_dest,PATHSIZE);

	//打开目录opendir
	DIR *dp_src = opendir(abs_src);
	if(dp_src == NULL)
	{
		perror("open src dir failed");
		exit(-1);
	}
			
	//读目录项(readdir)
	struct dirent *dp = NULL;//指针,指向读取到的目录项
	while(dp = readdir(dp_src))
	{
		chdir(abs_src);
		//排除.和..
		if(!strcmp(dp->d_name,".") || !strcmp(dp->d_name,".."))
		{
			continue;
		}

		//读取dp指向的目录项的属性,判断是文件还是目录
		struct stat st;
		stat(dp->d_name, &st);
		
		//子目录,递归调用函数
		if(S_ISDIR(st.st_mode))
		{
			//创建目标目录
			chdir(abs_dest); //进入目标目标
			mkdir(dp->d_name,0664); //创建同名的子目录
			chdir(dp->d_name); //进入新创建的子目录
			char path[PATHSIZE] = {0}; //保存新创建的子目录的绝对路径
			getcwd(path,PATHSIZE);

			chdir(abs_src);//回到源目录
			//把源目录下面的dp->d_name表示的目录复制为path表示的目录
			cp_dir(pool,dp->d_name,path);
		}else if(S_ISREG(st.st_mode))		//普通文件
		{
			//int fd[2] = {0}; //局部数组,在线程函数内部用到了
			int *fd = (int *)malloc(sizeof(int)*2);
			//fd[0] 源文件的文件描述符
			//fd[1] 目标文件的文件描述符
			fd[0] = open(dp->d_name,O_RDONLY);
			if(fd[0] == -1)
			{
				perror("open src_file failed");
				exit(-1);
			}
			chdir(abs_dest);
			fd[1] = open(dp->d_name,O_WRONLY | O_CREAT | O_TRUNC,0664);
			if(fd[1] == -1)
			{
				perror("open dest_file failed");
				exit(-1);
			}
			//复制文件描述符指定的文件
			//cp_file(fd);
			//创建一个线程,让线程去执行复制文件(注意路径)的操作
			//pthread_t tid;
			//pthread_create(&tid,NULL,cp_file, (void *)fd);
			//pthread_join(tid,NULL);

			//把当前复制文件的任务交给线程池(添加到线程池的任务队列)
			printf("111\n");
			add_task(pool,cp_file,(void *)fd);
			printf("333\n");
		}
	}
	//关闭前面打开的目录
	closedir(dp_src);
}


//把fds[0]表示的文件复制为fds[1]表示的文件
void *cp_file(void *arg)
{
	printf("复制文件!\n");
	int *fds = (int *)arg;//还原arg类型
	char buf[BUFSIZE] = {0};
	int nread,nwrite;
	while(1)
	{
		bzero(buf,BUFSIZE); //把buf表示的内存设置为0
		nread = read(fds[0],buf,BUFSIZE);
		if(nread == 0)
		{
			break;
		}else if(nread == -1)
		{
			perror("read fds[0] failed");
			exit(-1);
		}
		char *p = buf;
		while(nread > 0)
		{
			nwrite = write(fds[1],p,nread);
			nread -= nwrite;
			p+=nwrite;
		}
	}
	close(fds[0]);
	close(fds[1]);
	free(fds);
}

my_cpdir.h

#ifndef __MY_CPDIR_H__
#define __MY_CPDIR_H__


#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include "pthread_pool.h"


#define PATHSIZE 256
#define BUFSIZE 4096


/*
cp_dir:把src指向的目录复制为dest指向的目录
@pool:把要复制的文件任务添加到pool的任务队列
src:要复制的目录的路径名
dest:目标目录的路径名
*/
void cp_dir(pthread_pool *pool,const char *src,const char *dest);

//把fds[0]表示的文件复制为fds[1]表示的文件
//void cp_file(int *fds);
void *cp_file(void *fds);

#endif
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
线程池的原理已经在上面进行了解释,下面是一个使用线程池实现文件拷贝的C语言示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #define THREAD_POOL_SIZE 5 #define BUFFER_SIZE 1024 typedef struct { char src_file[256]; char dest_file[256]; } CopyTask; typedef struct { pthread_t thread; int is_working; } Worker; CopyTask task_queue[THREAD_POOL_SIZE]; Worker workers[THREAD_POOL_SIZE]; pthread_mutex_t mutex; pthread_cond_t cond; void *worker_thread(void *arg) { while (1) { pthread_mutex_lock(&mutex); // 等待任务到来 while (strlen(task_queue[*(int *)arg].src_file) == 0) { pthread_cond_wait(&cond, &mutex); } // 执行任务 CopyTask copy_task = task_queue[*(int *)arg]; task_queue[*(int *)arg].src_file[0] = '\0'; // 清空任务 pthread_mutex_unlock(&mutex); // 拷贝文件 FILE *src = fopen(copy_task.src_file, "rb"); FILE *dest = fopen(copy_task.dest_file, "wb"); if (src == NULL || dest == NULL) { printf("Failed to open file.\n"); continue; } char buffer[BUFFER_SIZE]; size_t bytesRead; while ((bytesRead = fread(buffer, sizeof(char), BUFFER_SIZE, src)) > 0) { fwrite(buffer, sizeof(char), bytesRead, dest); } fclose(src); fclose(dest); printf("File copied from %s to %s\n", copy_task.src_file, copy_task.dest_file); } } void thread_pool_init() { int i; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); for (i = 0; i < THREAD_POOL_SIZE; i++) { workers[i].is_working = 0; task_queue[i].src_file[0] = '\0'; pthread_create(&workers[i].thread, NULL, worker_thread, &i); } } void thread_pool_submit(char *src_file, char *dest_file) { pthread_mutex_lock(&mutex); // 查找空闲线程 int i; for (i = 0; i < THREAD_POOL_SIZE; i++) { if (strlen(task_queue[i].src_file) == 0) { strcpy(task_queue[i].src_file, src_file); strcpy(task_queue[i].dest_file, dest_file); pthread_cond_signal(&cond); break; } } pthread_mutex_unlock(&mutex); } int main() { int i; thread_pool_init(); // 提交任务 for (i = 0; i < 10; i++) { char src_file[256], dest_file[256]; sprintf(src_file, "source%d.txt", i); sprintf(dest_file, "destination%d.txt", i); thread_pool_submit(src_file, dest_file); } // 等待任务完成 sleep(1); return 0; } ``` 在上述示例中,我们定义了一个 `CopyTask` 结构体,用于存储拷贝任务的源文件和目标文件。线程池中的任务队列存储了 `CopyTask` 结构体的实例。 在主函数中,我们初始化了线程池,并提交了10个文件拷贝任务。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值