Linux下使用POSIX pthread从文件读整数到缓冲区的问题排查
问题分析与修复方案
我来帮你梳理一下代码里的几个关键问题,这些正是导致程序无限循环且反复读取第一个整数的核心原因:
1. 文件重复打开导致读取位置重置
每次进入写线程的临界区都执行fopen打开文件,这会让文件指针每次都回到文件开头,所以fscanf永远只能读取第一个整数,读完立刻fclose也完全丢失了读取进度。
修复方式:在main函数中提前打开文件,让所有写线程共享这个已打开的文件指针,直到所有数据读取完成后再统一关闭。
2. 缓冲区索引逻辑混乱
你代码里的z和y的计算完全不符合预期:每次进入循环y都会被重置为0,再y++后执行z = z + y,这会导致缓冲区索引的更新逻辑混乱,而且多线程操作z也没有正确的同步保护。
修复方式:用全局变量total_read记录已读取的整数总数,缓冲区索引用buf_count(当前缓冲区元素数量)来定位,所有对这些变量的修改都放在互斥锁的保护下。
3. 线程同步机制错误使用
- 你写了
while (cnt != 0)的空循环,但没有调用pthread_cond_wait,这会导致线程无意义忙等,完全起不到同步作用; - 互斥锁和条件变量没有初始化,会触发未定义行为;
- 修改
flag变量时没有加锁,线程间可能无法及时感知到flag的更新,导致无限循环。
修复方式:
- 在
main中初始化互斥锁和条件变量,程序结束后销毁它们; - 按照生产者-消费者模型设计同步逻辑:缓冲区满时生产者等待,缓冲区空时消费者等待,用条件变量实现线程的唤醒与阻塞。
4. feof判断时机错误
feof(fp)只有在fscanf尝试读取超过文件末尾时才会被标记为真,你在读完一个整数后就判断feof,会导致提前结束或无法正确检测到文件末尾。应该在fscanf返回值异常时再检查feof或ferror。
5. main函数参数错误
main的参数应该是int main(int argc, char *argv[]),而不是char argv[],这会导致编译错误或警告。
修复后的完整代码
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> pthread_mutex_t m; pthread_cond_t r; // 消费者等待条件 pthread_cond_t w; // 生产者等待条件 FILE *fp; int buf[20]; int buf_count = 0; // 缓冲区当前元素数量 int total_read = 0; // 已读取的整数总数 const int TOTAL_NUM = 100; // 需要读取的总数量 void *read_thread(void *parm); void *write_thread(void *parm); int main(int argc, char *argv[]) { // 初始化同步对象 pthread_mutex_init(&m, NULL); pthread_cond_init(&r, NULL); pthread_cond_init(&w, NULL); // 提前打开文件,避免重复打开重置读取位置 fp = fopen("/home/pegasus/2/shared_data.txt", "r"); if (fp == NULL) { fprintf(stderr, "Couldn't open the file\n"); exit(EXIT_FAILURE); } pthread_t rid[2]; pthread_t wid[2]; // 创建写线程(生产者) for (int i = 0; i < 2; i++) { printf("Writer %d Starts \n", i + 1); pthread_create(&wid[i], NULL, write_thread, (void *)(long)i); } // 若需要启用读线程,可取消以下注释 // for (int i = 0; i < 2; i++) { // printf("Reader %d Starts \n", i + 1); // pthread_create(&rid[i], NULL, read_thread, (void *)(long)i); // } // 等待写线程完成 for (int i = 0; i < 2; i++) { pthread_join(wid[i], NULL); } // 等待读线程完成(若启用) // for (int i = 0; i < 2; i++) { // pthread_join(rid[i], NULL); // } // 打印前3个缓冲区元素测试 for (int i = 0; i < 3; i++) { printf("buf[%d] = %d\n", i, buf[i]); } // 清理资源 fclose(fp); pthread_mutex_destroy(&m); pthread_cond_destroy(&r); pthread_cond_destroy(&w); return 0; } void *read_thread(void *parm) { int thread_id = (long)parm; while (1) { pthread_mutex_lock(&m); // 缓冲区为空时等待生产者 while (buf_count == 0) { printf("Reader %d is waiting for data\n", thread_id + 1); pthread_cond_wait(&r, &m); } // 读取缓冲区第一个元素(示例逻辑,可改为循环队列实现更高效) int val = buf[0]; printf("Reader %d read value: %d\n", thread_id + 1, val); // 缓冲区元素数量减一,前移元素 buf_count--; for (int i = 0; i < buf_count; i++) { buf[i] = buf[i + 1]; } // 唤醒等待的生产者 pthread_cond_signal(&w); pthread_mutex_unlock(&m); // 所有数据读完且缓冲区为空时退出 if (total_read >= TOTAL_NUM && buf_count == 0) { break; } sleep(1); } printf("Reader %d exits\n", thread_id + 1); return NULL; } void *write_thread(void *parm) { int thread_id = (long)parm; int value; while (1) { pthread_mutex_lock(&m); // 满足退出条件:已读满100个或文件已读完 if (total_read >= TOTAL_NUM || feof(fp)) { pthread_mutex_unlock(&m); break; } // 缓冲区满时等待消费者 while (buf_count == 20) { printf("Writer %d is waiting for buffer space\n", thread_id + 1); pthread_cond_wait(&w, &m); } // 读取一个整数,判断读取是否成功 int ret = fscanf(fp, "%d", &value); if (ret != 1) { if (feof(fp)) { printf("Writer %d reached end of file\n", thread_id + 1); } else { fprintf(stderr, "Writer %d failed to read data\n", thread_id + 1); } pthread_mutex_unlock(&m); break; } // 将数据写入缓冲区 buf[buf_count] = value; printf("Writer %d wrote value: %d to buf[%d], total read: %d\n", thread_id + 1, value, buf_count, ++total_read); buf_count++; // 唤醒等待的消费者 pthread_cond_signal(&r); pthread_mutex_unlock(&m); sleep(1); } printf("Writer %d exits\n", thread_id + 1); return NULL; }
关键修复点总结
- 文件仅打开一次,保留读取进度;
- 用
total_read和buf_count清晰跟踪读取状态,缓冲区索引逻辑明确; - 正确初始化和使用互斥锁、条件变量,实现标准的生产者-消费者同步;
- 修正
feof的判断时机,确保准确检测文件末尾; - 修复
main参数错误,保证编译正常; - 线程参数传递使用
(void *)(long)i,避免不同平台下的类型转换问题。
内容的提问来源于stack exchange,提问作者Pegasus008
相关产品推荐
相关产品推荐

