You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

模拟grep的C程序无线程正常,添加线程后出现无限循环问题求助

问题分析与修复方案

你的多线程grep程序出现无限循环、行号异常增长的核心原因是所有线程共享同一个文件读取上下文,重复处理相同内容,且行号计数全局共享导致累加混乱,同时原有的文件分片逻辑在多线程环境下存在指针维护错误,具体问题和修复方案如下:

核心问题总结

  1. 多线程任务划分错误:所有线程串行复用同一个文件读取上下文,既没有实现并行处理,还可能因文件指针维护逻辑缺陷导致重复读取,触发无限循环。
  2. 全局变量冲突:num_line被所有线程共享累加,导致行号无意义地疯长。
  3. 文件分片逻辑不安全: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(&regex, 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(&regex, 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(&regex);
    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;
}

关键修改说明

  1. 区间化并行处理:每个线程独立处理文件的一个子区间,彻底避免共享文件指针的冲突。
  2. 安全的行号计算:通过统计区间前的总行数,结合区间内的行数生成全局行号,不会出现累加混乱。
  3. 简化读取逻辑:使用getline逐行读取,避免了原代码中缓冲区拆分的复杂逻辑和越界风险。
  4. 有序输出控制:仅在输出匹配结果时使用互斥锁,保证输出行不混乱,其余操作完全并行。

编译命令:gcc -o main main.c -lpthread(需链接pthread库)

内容的提问来源于stack exchange,提问作者Fabián Vargas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 21:22:03