这是我所用的线程池,主任务为复制文件.首先是功能函数.c和.h文件
cp_dir.h
#ifndef __CP_DIR_H__
#define __CP_DIR_H__
#include "head.h"
#include "pthread_pool.h"
#define MAXNAME_LEN 4096
struct cp_file
{
//要复制的文件
char src[MAXNAME_LEN];
//目标文件
char dest[MAXNAME_LEN];
};
//将dir_src 指向的目录拷贝到dir_dest目录下
void cp_dir( pthread_pool *pool, char *dir_src, char *dir_dest);
//普通函数-->实现两个文件之间的拷贝
void cp_file(void *arg);
#endif
这是 cp_dir.c 文件
#include "cp_dir.h"
//普通函数--->实现两个文件之间的拷贝
void cp_file(void *arg)
{
//设置本线程为分离属性
//pthread_detach(pthread_self());
//将两个文件名解析出来
struct cp_file *p = (struct cp_file *)arg;
//以只读的方式去打开源文件
int fd_src = open(p->src,O_RDONLY);
if(fd_src == -1)
{
printf("%s",p->src);
fflush(stdout);
perror("open src file failed");
goto cp_return;
}
//以可读可写打开目标文件,如果目标文件不存在的话则创建,
//如果目标文件存在的话则截短。
int fd_dest = open(p->dest,O_RDWR | O_CREAT | O_TRUNC,0777);
if(fd_dest == -1)
{
perror("open dest file failed");
goto cp_return;
}
int ret;
char buf[1024];
//读取源文件的内容写入到目标文件中去
while(1)
{
ret = read(fd_src,buf,sizeof(buf));
if(ret == 0)
{
break;
}
else if(ret > 0)
{
int w = write(fd_dest,buf,ret);
if(w != ret)
{
perror("write failed");
}
}
else
{
perror("read error");
break;
}
}
cp_return:
close(fd_dest);
close(fd_src);
free(p);
}
//利用pool所指向的线程池将dir_src指向的目录拷贝到dir_dest目录下
void cp_dir(pthread_pool *pool,char *dir_src,char *dir_dest)
{
//获取到要拷贝的目录的目录名(不带路径的)
char *dirname = basename(dir_src);//dirname --> "20200907"
//根据刚才获取到的目录名和目标路径合成目标目录(带路径)
char newdirname[MAXNAME_LEN] = {0};
sprintf(newdirname,"%s/%s",dir_dest,dirname);//newdirname -> "/mnt/hgfs/CS20201/20200907"
printf("%s\n",newdirname);
//先创建目标目录
mkdir(newdirname,0777);
dir_dest = newdirname;
//打开要复制的目录
DIR *dir = opendir(dir_src);
if(dir == NULL)
{
perror("open src dir failed");
return ;
}
struct dirent *dirp = NULL;
//读取目录项
while(dirp = readdir(dir))
{
if(!strcmp(dirp->d_name,".") || !strcmp(dirp->d_name,".."))
{
continue;
}
char file_src[MAXNAME_LEN] = {0};
//获取到目录项的名字(带路径)
sprintf(file_src,"%s/%s",dir_src,dirp->d_name);
printf("file_src : %s\n",file_src);
//获取目录项的属性
struct stat st;
lstat(file_src,&st);
//如果是普通文件或者是链接文件就直接创建一个线程去拷贝即可
if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
{
//开辟一个线程去对两个文件实现直接拷贝
char file_dest[MAXNAME_LEN] = {0};
sprintf(file_dest,"%s/%s",dir_dest,dirp->d_name);
//此时要把源文件和目标文件都传递给线程函数
//所以需要把这个目标文件和源文件都保存到一个结构体中
//再把这个结构体传递给线程函数
struct cp_file *cp = malloc(sizeof(*cp));
strcpy(cp->src,file_src);
strcpy(cp->dest,file_dest);
printf("拷贝的两个文件名为:\n");
printf("src_file : %s\n",file_src);
printf("des_file : %s\n",file_dest);
printf("--------------------------------------\n");
/*
//创建一个线程去执行copy的任务
pthread_t tid;
int ret = pthread_create(&tid,NULL,cp_file,(void*)cp);
if(ret != 0)
{
perror("pthread create failed");
}
//pthread_join(tid,NULL);
*/
/*
int ret = pthread_create(&tids[tid_num++],NULL,cp_file,(void*)cp);
if(ret != 0)
{
perror("pthread create failed");
}
*/
add_task(pool,cp_file,(void *)cp);
}
else if(S_ISDIR(st.st_mode))
{
//如果是目录的话则递归调用字节
printf("拷贝的两个目录名为:\n");
printf("src_dir : %s\n",file_src);
printf("des_dir : %s\n",dir_dest);
printf("--------------------------------------\n");
cp_dir(pool,file_src,dir_dest);
}
else
{
printf("%s is UNKOWN TYPE!\n",file_src);
}
}
closedir(dir);
return ;
}
线程池头文件
pthread_pool_copy.h
#ifndef __PTHREAD_POOL_H__
#define __PTHREAD_POOL_H__
#include "head.h"
//线程池中线程的最大数量
#define MAX_THREADS_NUM 50
//线程池中任务队列最大的任务数量
#define MAX_TASKS 1000
typedef struct pthread_pool
{
//线程池的实现按照项目的不同也有所不同,但大体应该要有如下成员:
//因为“任务队列”是一种共享资源,所有我们需要互斥锁
//就是说我们需要一把互斥锁去保存“任务队列”
pthread_mutex_t lock;
//同时当我们的任务队列中没有任务的时候,线程池内的线程应该要休眠
//以避免系统资源的浪费,所有需要线程条件变量
//线程条件变量用来表示“任务队列”中是否有任务
pthread_cond_t cond;
//任务队列(链表),指向第一个需要执行的任务
//所有的线程都从这个任务链表中获取任务
struct task *task_list;
//指向线程ID的数组,用来保存线程池中所有线程的ID
pthread_t *tids;
//线程池中正在服役的线程数--->线程的个数
unsigned int active_threads;
//线程池中任务队列最大的任务数量
unsigned int max_waiting_tasks;
//线程池中任务队列当前的任务数量
unsigned int cur_waiting_tasks;
//表示是否退出程序
bool shutdown;//bool-->true(1) false(0)
}pthread_pool;
//任务节点
struct task
{
//每一个任务结点保存一个任务.
//所谓任务实际上就是把一个文件或目录从源目录下拷贝到目标目录下
//那么这个任务该如何去保存到任务节点上面去?
//任务的完成是通过函数来实现的,所以我们如果要去保存一个任务的话
//只需要保存完成任务的函数(cp_file)的指针既可以了。
//也就是说如果我们要去完成一个任务,就是去节点保存的地址上去
//执行一个函数就可以啦,函数只要执行完了,任务也就完成了。
//那么我们就需要定义一个函数指针。保存任务函数(cp_file)的地址
void (*do_task)(void *arg);
//另外,我们可能需要给任务函数传递参数(比如:文件名)
void *arg;
//下一个任务
struct task *next;
};
/*
init_pool:线程池的初始化函数
初始化指定的线程池的,线程池中有thread_num个初始化线程。
@pool:线程池指针,指向你要初始化的线程池
@thread_num:你要初始化的线程池中一开始线程的数量
@返回值:成功返回0,失败返回-1。
*/
int init_pool(phtread_pool *pool,unsigned int thread_num);
/*
routine:任务调配函数--->线程函数
所有线程开始都回去执行这个函数,此函数会不断的从线程池的任务队列中
取任务然后交给线程去执行
取任务--->arg表示的是线程池的指针,在线程池中有任务队列,任务队列中
有任务节点,每一个任务节点中都包含了函数指针和函数参数。
*/
void *routine(void *arg);
/*
销毁线程池:销毁线程池前要保证所有的任务都已经完成了
*/
int destroy_pool(pthread_pool *pool);
/*
add_task:给任务队列增加任务,把do_task指向的任务(函数指针)和arg指向的
参数保存到一个任务节点中去,同时将任务节点添加到pool表示的线程池的
任务队列中去。
@pool:指针,指向你要添加的任务的线程池
@fun_task:你要添加的任务(cp_file)
@fun_arg:你要执行的任务的参数(两个文件的文件名)
@返回值:成功返回0,失败返回-1。
*/
int add_task(pthread_pool *pool,void (*fun_task)(void *arg),void *fun_arg);
//往线程池中添加线程
int add_threads(pthread_pool *pool,unsigned int add_threads_num);
//删除线程池中的线程
int remove_threads(pthread_pool *pool,unsigned int remove_threads_num);
#endif
线程池函数
#include "pthread_pool.h"
/*
init_pool:线程池的初始化函数
初始化指定的线程池的,线程池中有thread_num个初始化线程。
@pool:线程池指针,指向你要初始化的线程池
@thread_num:你要初始化的线程池中一开始线程的数量
@返回值:成功返回0,失败返回-1。
*/
int init_pool(pthread_pool *pool,unsigned int thread_num)
{
//初始化线程池结构体
//初始化线程互斥锁
pthread_mutex_init(&pool->lock,NULL);
//初始化线程条件变量
pthread_cond_init(&pool->cond,NULL);
//创建一个任务节点,并且使用task_list去指向它
//需要注意的是第一个任务节点时没有初始化的,即没有值的
//因为对线程池进行初始化的时候,此时还没有任务
pool->task_list = (struct task *)malloc(sizeof(struct task));
pool->task_list->next = NULL;
//指向线程ID的数组进行初始化,此数组中保存了线程池中所有线程的ID
//用来保存线程ID的数组空间最好是用malloc进行开辟而且这块要足够大
pool->tids = (pthread_t *)malloc(sizeof(pthread_t)*MAX_THREADS_NUM);
if(pool->task_list == NULL || pool->tids == NULL)
{
perror("malloc memory failed");
return -1;
}
//线程池中正在服役的线程数量-->总的线程个数
pool->active_threads = thread_num;
//线程池中任务队列最大的任务数量
pool->max_waiting_tasks = MAX_TASKS;
//线程池中任务队列当前的任务数量
pool->cur_waiting_tasks = 0;
//不退出
pool->shutdown = false;
//创建thread_num个线程,同时记录所有线程的ID,让所有线程
//一开始就执行任务调配函数去执行任务。
int i;
for(i = 0;i < thread_num;i++)
{
if(pthread_create(&pool->tids[i],NULL,routine,(void *)pool) != 0)
{
perror("create thread failed");
return -1;
}
//打印调试信息
printf("[%lu] create tids[%d] : [%lu] is success!\n",pthread_self(),i,pool->tids[i]);
}
return 0;
}
//清理函数--->防止某一个线程带锁退出
void handler(void *arg)
{
pthread_mutex_unlock((pthread_mutex_t *)arg);
}
/*
routine:任务调配函数--->线程函数
所有线程开始都回去执行这个函数,此函数会不断的从线程池的任务队列中
取任务然后交给线程去执行
取任务--->arg表示的是线程池的指针,在线程池中有任务队列,任务队列中
有任务节点,每一个任务节点中都包含了函数指针和函数参数。
*/
void *routine(void *arg)
{
struct task *p = NULL;
//因为这个函数需要去访问任务队列,任务队列是共享资源
//所以需要上锁
//arg是你的线程池的指针.
pthread_pool *pool = (pthread_pool *)arg;
while(1)
{
//在上锁之前可以设置一个线程退出清理函数
//当线程因为某种意外情况导致线程夭折的时候自动执行
//我指定的清理函数(一般是解锁为了防止线程带锁退出)
//只能用在线程中
pthread_cleanup_push(handler,(void *)&pool->lock);
//获取到线程互斥锁,上锁
pthread_mutex_lock(&pool->lock);
//任务队列的情况分为很多种:
//1.任务队列没有任务并且线程池还没有结束的时候
//此时,在没有任务的时候(条件不满足的情况下),需要等待新任务的到来
//此处不能用if,必须要用while,因为一个线程被唤醒之后,有可能"抢不到"任务
while(pool->cur_waiting_tasks == 0 && !pool->shutdown)
{
//当前线程陷入休眠
pthread_cond_wait(&pool->cond,&pool->lock);
//当前线程被唤醒的条件:
//1.当有新任务的时候 2.当我要销毁线程池的时候
}
//2.任务队列中没有任务并且线程池要结束
if(pool->cur_waiting_tasks == 0 && pool->shutdown)
{
//解锁
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
}
//3.任务队列中有任务,将任务节点从任务链表中取下来
//找到任务链表中的第二个节点,因为第一个节点上面是空节点,没有任务
p = pool->task_list->next;
//摘除结点,更新链表
pool->task_list->next = p->next;
p->next = NULL;
pool->cur_waiting_tasks--;
//获取到线程互斥锁,解锁
pthread_mutex_unlock(&pool->lock);
//清理线程退出函数
pthread_cleanup_pop(0);
//当条件满足(任务队列中有任务的)的时候,去执行任务.
//执行拷贝任务的时候,拷贝任务是不能被打断的,所以我们要设置
//线程属性为不可被取消(干掉)
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
//按任务节点中的任务去执行任务(去执行cp_file函数)
(p->do_task)(p->arg);
//取消线程的不可被取消的属性
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
//释放任务节点
free(p);
}
}
/*
销毁线程池:销毁线程池前要保证所有的任务都已经完成了
*/
int destroy_pool(pthread_pool *pool)
{
//释放所有的空间等待任务执行完毕
pool->shutdown = true;//线程池要结束了
//唤醒所有的线程
pthread_cond_broadcast(&pool->cond);
//利用join函数回收每一个线程
int i;
for(i = 0;i < pool->active_threads;i++)
{
//从第0个线程开始回收
int r = pthread_join(pool->tids[i],NULL);
if(r != 0)
{
printf("Recover tids[%d] failed!\n",i);
}
else
{
printf("Recover tids[%d] success!\n",i);
}
}
//销毁线程互斥锁
pthread_mutex_destroy(&pool->lock);
//销毁线程条件变量
pthread_cond_destroy(&pool->cond);
//释放任务节点
if(pool->task_list)
{
free(pool->task_list);//只需要释放第一个节点即可
}
free(pool->tids);
free(pool);
return 0;
}
/*
add_task:给任务队列增加任务,把do_task指向的任务(函数指针)和arg指向的
参数保存到一个任务节点中去,同时将任务节点添加到pool表示的线程池的
任务队列中去。
@pool:指针,指向你要添加的任务的线程池
@fun_task:你要添加的任务(cp_file)
@fun_arg:你要执行的任务的参数(两个文件的文件名)
@返回值:成功返回0,失败返回-1。
*/
int add_task(pthread_pool *pool,void (*fun_task)(void *arg),void *fun_arg)
{
//在往任务队列中添加任务的时候,需要注意解锁和上锁
//加入任务后要唤醒等待的线程
//创建一个新的节点去保存任务
struct task *new_task = malloc(sizeof(*new_task));
new_task->next = NULL;
new_task->do_task = fun_task;
new_task->arg = fun_arg;
//把任务节点添加到任务链表中去
pthread_mutex_lock(&pool->lock);
//任务队列中的任务数量已达上限
if(pool->cur_waiting_tasks >= MAX_TASKS)
{
pthread_mutex_unlock(&pool->lock);
printf("Too many task,add task failed!\n");
//释放任务节点空间
free(new_task->arg);
free(new_task);
return -1;
}
//任务节点采用尾插法插入任务链表(此处不能头插)
struct task *find = pool->task_list;
while(find->next)
{
find = find->next;
}
//已经找到尾结点直接添加任务到链表中即可
find->next = new_task;
pool->cur_waiting_tasks++;
//访问完任务链表完之后解锁
pthread_mutex_unlock(&pool->lock);
//添加完之后,唤醒线程
pthread_cond_signal(&pool->cond);
return 0;
}
//往线程池中添加线程
int add_threads(pthread_pool *pool,unsigned int add_threads_num)
{
}
//删除线程池中的线程
int remove_threads(pthread_pool *pool,unsigned int remove_threads_num)
{
}
主函数头文件
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <libgen.h>
#endif
主函数
#include "cp_dir.h"
//./a.out dir_src dir_dest
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("Usage : %s <dir_src> <dir_dest>!\n",argv[0]);
return -1;
}
//创建一个线程池
pthread_pool *pool = malloc(sizeof(*pool));
//初始化线程池
int ret = init_pool(pool);
if(ret == -1)
{
printf("init pthread pool failed!\n");
return -1;
}
//利用线程池实现目录之间的拷贝
cp_dir(pool,argv[1],argv[2]);
destroy_pool(pool);
return 0;
}