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

C语言多线程字符统计存在竞态条件问题求助

多线程字母计数程序的竞态条件排查与修复

你编写的多线程统计文件字母出现次数的C程序,在使用1个以上线程时每次运行结果不一致,即使加了互斥锁问题依然存在,核心问题出在线程参数传递的竞态上,而非互斥锁的使用。

问题代码

#define N_LETTERS 26U

#define IS_LETTER(x) (x >= 'A' && x <= 'Z')
#define HASH(x) toupper(x) - 'A'

typedef unsigned int uint;

typedef struct {
    uint nt;
    uint nc;
    char filename[128];
} Args;

Args args = {0};
char *file_contents = NULL;
uint file_size;

uint chars_per_thread;
pthread_mutex_t mutex;
uint letter_count[N_LETTERS] = {0};

void get_program_args(int argc, char **argv) {
    ...
}

uint read_file(char *filename, char **buffer) {
    ....

    return file_size;
}

void *count_letters(void *arg) {
    uint start = *((int *)arg);
    uint end = (start + chars_per_thread) >= file_size ? file_size : (start + chars_per_thread);
    uint count[N_LETTERS] = {0};

    for (uint i = start; i < end; i++) {
        char c = toupper(file_contents[i]);

        if (IS_LETTER(c)) {
            count[HASH(c)]++;
        }
    }

    pthread_mutex_lock(&mutex);
    for (int i = 0; i < N_LETTERS; i++) {
        letter_count[i] += count[i];
    }
    pthread_mutex_unlock(&mutex);

    return NULL;
}

float letter_sum() {
    ...
    return sum;
}

void print_letter_count() {
...
}

int main(int argc, char **argv) {
    uint chars_counted = 0;

    get_program_args(argc, argv);
    file_size = read_file(args.filename, &file_contents);

    pthread_mutex_init(&mutex, NULL);

    pthread_t *threads = malloc(args.nt * sizeof(pthread_t));
    if (threads == NULL) {
        printf("Error allocating memory. \n");
        exit(EXIT_FAILURE);
    }

    chars_per_thread = (file_size / args.nt) > args.nc ? args.nc : (file_size / args.nt);

    while (chars_counted < file_size) {
        for (int i = 0; i < args.nt; i++) {
            uint start = chars_counted;

            if (start >= file_size)
                break;

            pthread_create(&threads[i], NULL, count_letters, &start);

            chars_counted += chars_per_thread;
        }

        for (int i = 0; i < args.nt; i++) {
            pthread_join(threads[i], NULL);
        }
    }

    pthread_mutex_destroy(&mutex);
    free(threads);
    free(file_contents); // free allocated memory

    return 0;
}

问题根源

在main函数的线程创建循环中,你传递给pthread_create的参数是局部变量start的地址&start,但主线程不会等待线程函数读取这个值就会继续执行:

  • 主线程调用pthread_create后立刻执行chars_counted += chars_per_thread,后续循环中start会被重新赋值。
  • 线程函数count_letters真正执行时,读取的start值可能已经被主线程修改,导致多个线程拿到相同或错误的起始位置,统计范围重叠或遗漏,最终结果混乱。

互斥锁只保护了全局letter_count的累加操作,但线程参数传递的竞态根本没被解决。

修复方案

为每个线程分配独立的内存存储起始位置,避免共享同一个变量的地址:

方案1:使用数组存储每个线程的起始值

修改main函数中的线程创建逻辑:

int main(int argc, char **argv) {
    uint chars_counted = 0;

    get_program_args(argc, argv);
    file_size = read_file(args.filename, &file_contents);

    pthread_mutex_init(&mutex, NULL);

    pthread_t *threads = malloc(args.nt * sizeof(pthread_t));
    if (threads == NULL) {
        printf("Error allocating memory. \n");
        exit(EXIT_FAILURE);
    }
    // 为每个线程分配独立的起始位置存储
    uint *thread_starts = malloc(args.nt * sizeof(uint));
    if (thread_starts == NULL) {
        printf("Error allocating memory. \n");
        free(threads);
        exit(EXIT_FAILURE);
    }

    chars_per_thread = (file_size / args.nt) > args.nc ? args.nc : (file_size / args.nt);

    while (chars_counted < file_size) {
        int thread_num = 0;
        for (; thread_num < args.nt; thread_num++) {
            uint start = chars_counted;
            if (start >= file_size)
                break;
            
            thread_starts[thread_num] = start;
            pthread_create(&threads[thread_num], NULL, count_letters, &thread_starts[thread_num]);

            chars_counted += chars_per_thread;
        }

        // 只join实际创建的线程
        for (int i = 0; i < thread_num; i++) {
            pthread_join(threads[i], NULL);
        }
    }

    print_letter_count();

    pthread_mutex_destroy(&mutex);
    free(threads);
    free(thread_starts); // 释放线程起始值内存
    free(file_contents);

    return 0;
}

方案2:动态分配每个线程的参数

在创建线程时,为每个start值单独malloc内存,线程函数使用完后释放:

void *count_letters(void *arg) {
    uint start = *((uint *)arg);
    free(arg); // 释放动态分配的参数内存
    uint end = (start + chars_per_thread) >= file_size ? file_size : (start + chars_per_thread);
    uint count[N_LETTERS] = {0};

    for (uint i = start; i < end; i++) {
        char c = toupper(file_contents[i]);

        if (IS_LETTER(c)) {
            count[HASH(c)]++;
        }
    }

    pthread_mutex_lock(&mutex);
    for (int i = 0; i < N_LETTERS; i++) {
        letter_count[i] += count[i];
    }
    pthread_mutex_unlock(&mutex);

    return NULL;
}

// main函数中的创建逻辑片段
for (int i = 0; i < args.nt; i++) {
    uint start = chars_counted;
    if (start >= file_size)
        break;
    
    uint *start_ptr = malloc(sizeof(uint));
    *start_ptr = start;
    pthread_create(&threads[i], NULL, count_letters, start_ptr);

    chars_counted += chars_per_thread;
}

额外优化

  • 可以直接计算每个线程的起始位置,无需依赖chars_counted的循环累加,进一步避免变量共享问题。
  • 注意thread_join的范围,只join实际创建的线程,避免join未初始化的线程句柄。

内容的提问来源于stack exchange,提问作者Diogo Ferreira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:03:17