模拟grep的C程序无线程正常,添加线程后出现无限循环问题求助
问题分析与修复方案
你的多线程grep程序出现无限循环、行号异常增长的核心原因是所有线程共享同一个文件读取上下文,重复处理相同内容,且行号计数全局共享导致累加混乱,同时原有的文件分片逻辑在多线程环境下存在指针维护错误,具体问题和修复方案如下:
核心问题总结
- 多线程任务划分错误:所有线程串行复用同一个文件读取上下文,既没有实现并行处理,还可能因文件指针维护逻辑缺陷导致重复读取,触发无限循环。
- 全局变量冲突:
num_line被所有线程共享累加,导致行号无意义地疯长。 - 文件分片逻辑不安全:
restore_offset中的越界访问(当片段内无换行符时)会导致未定义行为,进一步加剧循环问题。
修复方案
1. 重新设计线程任务模型
将文件划分为独立区间,每个线程负责一个区间的读取、匹配,完全避免共享文件指针和读取上下文,真正实现并行处理。
2. 独立维护行号计数
每个线程先统计自身区间之前的总行数,再结合区间内的行数生成全局正确的行号,避免共享变量冲突。
3. 简化文件读取逻辑
使用getline逐行读取,替代原有的缓冲区拆分逻辑,避免越界和分片错误。
修改后的代码示例
#include "regex.h" #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #define BUFFER_SIZE 1024 #define NUM_THREADS 5 typedef struct { char *pattern; char *filename; } Parameter_Handler; typedef struct { int thread_id; long long start_offset; long long end_offset; char *pattern; char *filename; pthread_mutex_t *output_mutex; } Thread_Data; // 编译正则表达式,失败时输出错误信息 int compile_regex(regex_t *regex, const char *pattern) { int ret = regcomp(regex, pattern, REG_EXTENDED | REG_NEWLINE); if (ret != 0) { char err_buf[1024]; regerror(ret, regex, err_buf, sizeof(err_buf)); fprintf(stderr, "Regex compile error: %s\n", err_buf); return ret; } return 0; } // 计算文件起始到指定偏移量的总行数 long long count_lines_before_offset(FILE *file, long long offset) { long long count = 0; char buf[BUFFER_SIZE]; size_t bytes_read; fseek(file, 0, SEEK_SET); while (offset > 0) { bytes_read = fread(buf, 1, (size_t)offset < BUFFER_SIZE ? offset : BUFFER_SIZE, file); if (bytes_read == 0) break; for (size_t i = 0; i < bytes_read; i++) { if (buf[i] == '\n') count++; } offset -= bytes_read; } return count; } // 线程核心处理函数:负责指定区间的文件内容匹配与输出 void *process_file_segment(void *arg) { Thread_Data *data = (Thread_Data *)arg; FILE *file = fopen(data->filename, "r"); if (!file) { perror("Failed to open file"); pthread_exit(NULL); } regex_t regex; if (compile_regex(®ex, data->pattern) != 0) { fclose(file); pthread_exit(NULL); } // 定位到线程负责的起始位置 fseek(file, data->start_offset, SEEK_SET); char *line = NULL; size_t line_len = 0; ssize_t bytes_read; long long local_line_num = 0; long long global_line_base = count_lines_before_offset(file, data->start_offset); // 重置指针到区间起始点 fseek(file, data->start_offset, SEEK_SET); // 逐行读取并匹配 while ((bytes_read = getline(&line, &line_len, file)) != -1) { long long current_offset = ftell(file); if (current_offset > data->end_offset) break; local_line_num++; long long global_line_num = global_line_base + local_line_num; // 去除换行符 if (line[bytes_read - 1] == '\n') { line[bytes_read - 1] = '\0'; } // 匹配成功则输出(加锁保证输出有序) if (regexec(®ex, line, 0, NULL, 0) == 0) { pthread_mutex_lock(data->output_mutex); printf("[%lld] %s\n", global_line_num, line); pthread_mutex_unlock(data->output_mutex); } } // 清理资源 regfree(®ex); fclose(file); free(line); pthread_exit(NULL); } int main(int argc, char *argv[]) { if (argc < 4 || strcmp(argv[1], "grep") != 0) { fprintf(stderr, "Usage: %s grep <pattern> <file.txt>\n", argv[0]); exit(EXIT_FAILURE); } Parameter_Handler ph = { .pattern = argv[2], .filename = argv[3] }; // 获取文件总大小 struct stat file_stat; if (stat(ph.filename, &file_stat) != 0) { perror("Failed to get file stats"); exit(EXIT_FAILURE); } long long file_size = file_stat.st_size; // 初始化线程数据与同步锁 Thread_Data thread_data[NUM_THREADS]; pthread_t threads[NUM_THREADS]; pthread_mutex_t output_mutex; pthread_mutex_init(&output_mutex, NULL); // 划分文件区间 long long segment_size = file_size / NUM_THREADS; for (int i = 0; i < NUM_THREADS; i++) { thread_data[i].thread_id = i; thread_data[i].start_offset = i * segment_size; // 最后一个线程处理剩余所有内容 thread_data[i].end_offset = (i == NUM_THREADS - 1) ? file_size : (i + 1) * segment_size; thread_data[i].pattern = ph.pattern; thread_data[i].filename = ph.filename; thread_data[i].output_mutex = &output_mutex; if (pthread_create(&threads[i], NULL, process_file_segment, &thread_data[i]) != 0) { perror("Failed to create thread"); exit(EXIT_FAILURE); } } // 等待所有线程完成 for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } pthread_mutex_destroy(&output_mutex); return EXIT_SUCCESS; }
关键修改说明
- 区间化并行处理:每个线程独立处理文件的一个子区间,彻底避免共享文件指针的冲突。
- 安全的行号计算:通过统计区间前的总行数,结合区间内的行数生成全局行号,不会出现累加混乱。
- 简化读取逻辑:使用
getline逐行读取,避免了原代码中缓冲区拆分的复杂逻辑和越界风险。 - 有序输出控制:仅在输出匹配结果时使用互斥锁,保证输出行不混乱,其余操作完全并行。
编译命令:gcc -o main main.c -lpthread(需链接pthread库)
内容的提问来源于stack exchange,提问作者Fabián Vargas
相关产品推荐
相关产品推荐

