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
相关产品推荐
相关产品推荐

