C语言线程池实现大目录拷贝,解决堆栈溢出问题
#include"myhead.h" //清除函数 void handler(void *arg) { \tprintf("[%u] is ended.\n",\n\t\t(unsigned)pthread_self());\n\n\t//解锁\n\tpthread_mutex_unlock((pthread_mutex_t *)arg); } //线程执行函数 void *routine(void *arg) { \t//1.先接住线程池的地址 \tthread_pool *pool = (thread_pool *)arg; \tstruct task *p; \t//2.执行操作 \twhile(1) \t{ \t\t//2.线程取消例程函数 \t\t//将来要是有人想取消一个线程,要先把线程拥有的锁解开,再响应取消 \t\tpthread_cleanup_push(handler, (void *)&pool->lock); \t\t//3.上锁,临界资源->任务队列 \t\tpthread_mutex_lock(&pool->lock); \t\t//4.如果当前线程池没有关闭,并且当前线程池没有任务做 \t\twhile(pool->waiting_tasks == 0 && !pool->shutdown) \t\t{ \t\t\t//那么就进去条件变量中睡觉。 \t\t\t//printf("[%u] ==>睡\n",(unsigned)pthread_self()); \t\t\tpthread_cond_wait(&pool->cond, &pool->lock); //线程睡眠,自动解锁\n\t\t\t\n\t\t} \t\t//5.要是线程池关闭了,或者有任务做,这些线程就会运行到这行代码 \t\t//判断当前线程池是不是关闭了,并且没有等待的任务 \t\tif(pool->waiting_tasks == 0 && pool->shutdown == true) \t\t{ \t\t\t//如果线程池关闭,又没有任务做 \t\t\t//线程那么就会解锁 \t\t\tpthread_mutex_unlock(&pool->lock);\t \t\t\tpthread_exit(NULL); \n\t\t} \t\t//6.能运行到这里,说明有任务做 \t\t//p指向头节点的下一个 \t\tp = pool->task_list->next; \t\t\n\t\t//7.让头节点的指针域指向p的下一个节点 \t\tpool->task_list->next = p->next; \t\t\n\t\t//8.当前任务个数-1 \t\tpool->waiting_tasks--; \t\t\n\t\t//9.解锁 \t\tpthread_mutex_unlock(&pool->lock); \t\t\n\t\t//10.删除线程取消例程函数 \t\tpthread_cleanup_pop(0); \t\t\n\t\t//11.线程要执行任务函数,设置线程不能响应取消 \t\tpthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); \n\t\t\n\t\t//执行这个p节点指向的节点的函数 \t\t(p->do_task)(p->arg); \t\t\n\t\t//任务函数执行完毕,设置线程能响应取消 \t\tpthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); \t\t\n\t\t//执行完了,释放p对应的空间 \t\tfree(p); \t} \tpthread_exit(NULL); } //初始化线程池 bool init_pool(thread_pool *pool, unsigned int threads_number) { \t//1.初始化线程池互斥锁 \tpthread_mutex_init(&pool->lock, NULL); \t//2.初始化条件变量 \tpthread_cond_init(&pool->cond, NULL); \t//3.初始化标志位为false,代表当前线程池正在运行 \tpool->shutdown = false; \t//4.初始化任务队列头节点 \tpool->task_list = malloc(sizeof(struct task)); \t//5.为存储线程ID号tids申请空间 \tpool->tids = malloc(sizeof(pthread_t)*MAX_ACTIVE_THREADS); \t//6.对第四步和第五步做错误判断 \tif(pool->task_list == NULL || pool->tids == NULL) \t{ \t\tperror("任务队列|线程号指针 内存失败\n"); \t\treturn false; \t} \t//7.为线程池任务队列头节点指针域赋值NULL \tpool->task_list->next == NULL; \t//8.使用宏定义来设置线程池最大任务个数为1000 \tpool->max_waiting_tasks = MAX_WAITING_TASKS; \t//9.设置当前需要处理任务数位0 \tpool->waiting_tasks = 0; \t//10.初始化线程池中线程的个数 \tpool->active_threads = threads_number; \t//11.创建线程 \tfor(int i = 0; i < pool->active_threads; i++) \t{ \t\tif(pthread_create(&(pool->tids[i]), NULL, routine, (void *)pool) != 0) \t\t{ \t\t\tperror("线程创建失败\n"); \t\t\treturn false; \t\t} \t} \t//12.线程初始化成功,返回true \treturn true; } //增添任务 bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg) { \t//1.为新任务的节点申请空间 \tstruct task *new_task = malloc(sizeof(struct task)); \tif(new_task == NULL) \t{ \t\tperror("新任务申请空间失败\n"); \t\treturn false; \t} \t//2.为新节点的数据域与指针域赋值 \tnew_task->do_task = do_task; \tnew_task->arg = arg; \tnew_task->next = NULL; \t//3.在添加任务到任务队列前,必须先上锁,因为添加任务属于访问临界资源->即任务队列 \tpthread_mutex_lock(&pool->lock); \t//4.如果当前需要等待处理的任务个数已经超过或等于1000,就解锁,并释放掉全部刚刚创建的新节点 \tif(pool->waiting_tasks >= MAX_WAITING_TASKS) \t{ \t\tpthread_mutex_unlock(&pool->lock); \t\tfprintf(stderr, "任务过多\n"); \t\tfree(new_task); \t\treturn false; \t} \t//5.寻找任务队列中的最后一个节点 \tstruct task *tmp = pool->task_list; \twhile(tmp->next != NULL) \t{ \t\ttmp = tmp->next; \t} \t//6.尾插到任务队列中 \ttmp->next = new_task; \t//7.将当前需要等待处理的任务个数+1 \tpool->waiting_tasks++; \t//8.新任务添加完毕,解锁 \tpthread_mutex_unlock(&pool->lock); \t//9.任务已添加,随机唤醒一个条件变量中睡眠的线程起来做任务 \tpthread_cond_signal(&pool->cond); \t//添加成功,唤醒成功,返回true \treturn true; } //添加线程 int add_thread(thread_pool *pool, unsigned additional_threads) { \t//1.如果创建条数为0,则不添加,直接返回0 \tif(additional_threads == 0) \t{ \t\treturn 0; \t} \t//2.将初始化的线程个数与新添加的线程个数相加赋值给总线程条数 \tunsigned total_threads = pool->active_threads + additional_threads; \t//3.循环create创建新线程,并将实际创建条数+1,若创建后实际创建成功的线程条数为0,则退出返回-1 \tint i = 0, actual_increment = 0; //实际创建条数 \tfor(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++) \t{ \t\tif(pthread_create(&(pool->tids[i]), NULL, routine, (void *)pool) != 0) \t\t{ \t\t\tperror("新线程创建失败\n"); \t\t\treturn false; \t\t} \t} \t//4.将实际创建成功个数加入需要等待处理的线程条数 \tpool->active_threads += actual_increment; \t//5.返回真正创建的条数 \treturn actual_increment; } //取消删除线程 int remove_thread(thread_pool *pool, unsigned int removing_threads) { \t//1.如果想删0条,不给删,直接返回剩余条数 \tif(removing_threads == 0) \t{ \t\treturn pool->active_threads; //这里相当于剩余条数 \t} \t//2.求出取消后剩余线程条数 \tint remaining_threads = pool->active_threads - removing_threads; \tremaining_threads = remaining_threads > 0 ? remaining_threads : 1; \t//3.循环取消 \tint i = 0; \tfor(i = pool->active_threads - 1; i > remaining_threads - 1; i--) \t{ \t\terno = pthread_cancel(pool->tids[i]); \t\tif(errno != 0) //不为0说明删除失败 \t\t{ \t\t\tbreak; \t\t} \t} \t//i不变不减小,说明删除失败,然后就返回 \tif(i == pool->active_threads - 1) \t{ \t\treturn -1; \t} \telse \t{ \t\tpool->active_threads = i + 1; //i为下标,下标+1为实际剩余条数 \t} \treturn i + 1; //返回删除后剩余条数 } //销毁线程池 bool destroy_pool(thread_pool *pool) { \t//1.设置线程池为关闭 \tpool->shutdown = false; \t//2.唤醒所有睡眠的线程,目的:方便后续让线程退出 \tpthread_cond_broadcast(&pool->cond); \t//3.结合所有线程 \tint i = 0; \tfor(i = 0; i < pool->active_threads; i++) \t{ \t\terno = pthread_join(pool->tids[i], NULL); \t\tif(errno != 0) //不为0说明接合失败 \t\t{ \t\t\tprintf("join tids[%d] error: %s\n", i, strerror(errno)); \t\t} \t\telse \t\t{ \t\t\tprintf("[%u] is joined\n", (unsigned)pool->tids[i]); \t\t} \t} \t//4.释放线程池中动态开辟的一些空间 \tfree(pool->task_list); \tfree(pool->tids); \tfree(pool); \t//成功销毁,返回true \treturn true; } //目录复制 int copy_any() { \tchar source_dir[200]; //源路径 \tchar copy_dir[200]; //复制地路径 \tchar source_backdirname[50]; //源路径初始文件夹名反序 \tchar source_dirname[50]; //源路径初始文件夹名正序 \twhile(1) \t{ \t\tbzero(source_dir, sizeof(source_dir)); \t\tbzero(copy_dir, sizeof(copy_dir)); \t\tbzero(source_backdirname, sizeof(source_backdirname)); \t\tbzero(source_dirname, sizeof(source_dirname)); \t\tprintf("请输入需要复制的源路径:"); \t\tscanf("%s", source_dir); \t\tprintf("请输入复制地路径:"); \t\tscanf("%s", copy_dir); \t\tprintf("源1:%s\n", source_dir); \t\tprintf("复制1:%s\n", copy_dir); \t\tint j = 0; \t\tfor(int i = strlen(source_dir) - 1; source_dir[i] != '/'; i--) //文件夹名反序到source_backdirname中 \t\t{ \t\t\tsource_backdirname[j] = source_dir[i]; \t\t\tj++; \t\t} \t\tj = 0; \t\tprintf("strlen(source_backdirname):%ld\n", strlen(source_backdirname)); \t\tfor(int i = strlen(source_backdirname) - 1; i >= 0; i--) //正序输入到source_dirname中 \t\t{ \t\t\tsource_dirname[j] = source_backdirname[i]; \t\t\tj++; \t\t} \t\tstruct file_path *init_path = malloc(sizeof(file_path)); //为文件路径结构体开辟内存空间 \t\tmemset(init_path, 0, sizeof(file_path)); //对内存清零 \t\tsprintf(init_path->source_path, "%s", source_dir); //拼凑源文件路径 \t\tsprintf(init_path->copy_path, "%s/%s", copy_dir, source_dirname); //拼凑目标文件路径 \t\tprintf("源:%s\n", init_path->source_path); \t\tprintf("复制:%s\n", init_path->copy_path); \t\tcopy_wholedir(init_path); \t\tfree(init_path); \t\treturn 0; \t} } //复制文件夹中单个文件 void *copy_single(void *arg) { \tstruct file_path *tmp_path = (struct file_path *)arg; \tchar buf[10000]; \tbzero(buf, sizeof(buf)); \tprintf("源目录单文件:%s\n", tmp_path->source_path); \tint fd3 = open(tmp_path->source_path, O_RDWR); //打开源文件 \tif(fd3 == -1) \t{ \t\tprintf("源目录中单文件打开失败\n"); \t\treturn NULL; \t} \tlong len = lseek(fd3, 0, SEEK_END); //读取文件字节总数 \tlseek(fd3, 0, SEEK_SET); //将光标重新移至开头 \tread(fd3, buf, len); //读取内容,存入buf \n\tint fd4 = open(tmp_path->copy_path, O_RDWR | O_CREAT, 0777); //创建并打开复制文件 \tif(fd4 == -1) \t{ \t\tprintf("复制地目录中单文件打开失败\n"); \t\treturn NULL; \t} \twrite(fd4, buf, len); //写入内容 \tclose(fd3); //关闭文件 \tclose(fd4); //关闭文件 \treturn NULL; } //复制整个文件夹 int copy_wholedir(struct file_path *init_path) { \tmkdir(init_path->copy_path, 0777); \tDIR *dp3 = opendir(init_path->copy_path); //打开复制地路径目录 \tif(dp3 == NULL) \t{ \t\tprintf("复制地路径不存在,请重新输入\n"); \t\treturn 0; \t} \n\tDIR *dp2 = opendir(init_path->source_path); //打开源路径目录 \tif(dp2 == NULL) \t{ \t\tprintf("源路径不存在,请重新输入\n"); \t\treturn 0; \t} \tchdir(init_path->source_path); //切换到源路径目录 \tstruct dirent *ep2 = NULL; \t \twhile(1) \t{ \t\tep2 = readdir(dp2); //读取路径下的文件内容 \t\tif(ep2 == NULL) //读完了也没找到,退出 \t\t{ \t\t\tbreak; \t\t} \t\tif(ep2->d_name[0] == '.') //去掉.和..\n\t\t{ \t\t\tcontinue; \t\t} \t\tstruct file_path *tmp_path = malloc(sizeof(file_path)); //为文件结构体开辟内存空间 \t\tmemset(tmp_path, 0, sizeof(file_path)); //对内存清零 \t\t \t\tsprintf(tmp_path->source_path, "%s/%s", init_path->source_path, ep2->d_name); //拼凑源文件路径 \t\tsprintf(tmp_path->copy_path, "%s/%s", init_path->copy_path, ep2->d_name); //拼凑目标文件路径 \t\t \t\tif(ep2->d_type == 4) \t\t{ \t\t\t//mkdir(tmp_path->copy_path, 0777); //在复制地路径下创建与源路径同名文件夹 \t\t\tcopy_wholedir(tmp_path); \t\t\tfree(tmp_path); // 释放之前创建的file_path结构体 \t\t} \t\tif(ep2->d_type == 8) \t\t{ \t\t\tprintf("ep2->d_name:%s\n", ep2->d_name); \t\t\tprintf("源目录文件为:%s\n", tmp_path->source_path); \t\t\t//printf("复制地目录文件为:%s", tmp_path->copy_path); \t\t\tadd_task(pool, copy_single, (void *)tmp_path); \t\t\tfree(tmp_path); // 释放之前创建的file_path结构体 \t\t} \t} \t//=free(tmp_path); \tclosedir(dp2); \tclosedir(dp3); \tfree(init_path); // 释放初始传入的file_path结构体 \treturn 0; } 这段代码的功能是使用线程池完成大目录拷贝,但是文件内容都能够复制,但最后这段代码会发生堆栈溢出,为什么? 内容:这段代码可能会导致堆栈溢出的原因是在复制整个文件夹的过程中,每次递归调用copy_wholedir函数时都会创建一个新的file_path结构体,并将其作为参数传递给add_task函数。然而,在每次递归调用时,没有释放之前创建的file_path结构体,导致内存泄漏。随着递归调用的深入,堆栈中的内存空间被不断占用,最终导致堆栈溢出。
要解决这个问题,可以在每次递归调用copy_wholedir函数之前,先释放之前创建的file_path结构体,代码示例如下:
int copy_wholedir(struct file_path *init_path)
{
// ...
while (1)
{
// ...
if (ep2->d_type == 4)
{
copy_wholedir(tmp_path);
free(tmp_path); // 释放之前创建的file_path结构体
}
if (ep2->d_type == 8)
{
// ...
}
}
// ...
closedir(dp2);
closedir(dp3);
free(init_path); // 释放初始传入的file_path结构体
return 0;
}
通过在递归调用前释放之前创建的file_path结构体,可以避免堆栈溢出的问题。
原文地址: https://www.cveoy.top/t/topic/ok81 著作权归作者所有。请勿转载和采集!