线程池的概念及实现(文件夹的拷贝)
一、普通的线程使用
使用线程的方式复制文件(任务)可能会产生一些问题:
-
copy文件的时候,本质上还是"串行"的
开一个线程去复制文件的时候,主线程会因为pthread_join而阻塞
如果不使用pthread_join去等待子线程结束
有可能会任务线程被强制退出(进程结束了,但是复制文件的线程还没有结束)
导致有一部分文件可能复制不完全 -
也可能导致资源浪费(线程分离)
可能创建的线程太多了(发现一个文件就会创建一个线程)
线程池的模式(是一种解决问题的思路/方案)
二、线程池
线程池(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