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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:25:40