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

Linux下C程序:主线程正常的函数在线程内无法执行问题排查

问题分析与修复:Linux多线程inotify文件复制程序异常

问题描述

我正在编写一款Linux环境下的C程序,功能是将当前目录中的文件内容追加至公共文件。程序要求线程数量可调且线程为永久线程(执行完毕后等待再次触发执行)。下方代码将新创建的文件分配给响应最快的线程,由线程调用copiar_archivo函数完成内容追加,但该函数完全未执行,程序直接跳过调用并删除文件。我已验证该函数在主线程中指定具体文件名时可正常工作,怀疑问题与线程环境或inotify事件相关,请问遗漏了什么?

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/inotify.h>

#define EVENT_SIZE  (sizeof (struct inotify_event))
#define BUF_LEN     (1024 * (EVENT_SIZE + 16))
#define NOM_FICH "FICHERO.csv"

void *hilo_func(void *arg);
void copiar_archivo(char *nombre_archivo_x, char *nombre_archivo_y);

int n_hilos = 2;
int fd, wd;

int main(void) {
    pthread_t hilos[n_hilos];
    int i, rc;
    int thread_numbers[n_hilos];

    fd = inotify_init();
    if (fd < 0) {
        perror("Error al inicializar inotify");
        exit(EXIT_FAILURE);
    }

    wd = inotify_add_watch(fd, ".", IN_CREATE);
    if (wd < 0) {
        perror("Error al agregar directorio para observación");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < n_hilos; i++) {
        thread_numbers[i] = i + 1;
        rc = pthread_create(&hilos[i], NULL, hilo_func, &thread_numbers[i]);
        if (rc) {
            perror("Error al crear hilo");
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < n_hilos; i++) {
        rc = pthread_join(hilos[i], NULL);
        if (rc) {
            perror("Error al esperar hilo");
            exit(EXIT_FAILURE);
        }
    }

    inotify_rm_watch(fd, wd);
    close(fd);

    return 0;
}

void *hilo_func(void *arg) {
    int num_hilo = *(int *) arg;
    char buf[BUF_LEN];
    int length, i = 0;

    while (1) {
        length = read(fd, buf, BUF_LEN);
        printf("Hilo %d: AWARE\n", num_hilo);
        if (length < 0) {
            perror("Error al leer evento inotify");
            exit(EXIT_FAILURE);
        }

        while (i < length) {
            struct inotify_event *event = (struct inotify_event *) &buf[i];
            if (event->mask & IN_CREATE) {
                printf("Hilo %d: archivo '%s' creado\n", num_hilo, event->name);
               
                //PROBLEM HERE
                char nom_destino[100] = NOM_FICH;
                copiar_archivo(event->name, nom_destino);
               
                sleep(1);
                remove(event->name);
            }
            i += EVENT_SIZE + event->len;
        }
        i = 0;
    }

    pthread_exit(NULL);
}

void copiar_archivo(char *nombre_archivo_x, char *nombre_archivo_y) {
    printf("EY, al menos funciona :)");
    FILE *archivo_x, *archivo_y;
    char buffer[1024];
    size_t n;

    archivo_x = fopen(nombre_archivo_x, "rb");
    if (archivo_x == NULL) {
        perror("Error al abrir archivo X");
        exit(EXIT_FAILURE);
    }

    archivo_y = fopen(nombre_archivo_y, "ab");
    if (archivo_y == NULL) {
        perror("Error al abrir archivo Y");
        exit(EXIT_FAILURE);
    }

    while ((n = fread(buffer, 1, sizeof buffer, archivo_x)) > 0) {
        fwrite(buffer, 1, n, archivo_y);
    }

    if (ferror(archivo_x)) {
        perror("Error al leer archivo X");
        exit(EXIT_FAILURE);
    }

    fclose(archivo_x);
    fclose(archivo_y);
}

核心问题与修复方案

1. 文件创建事件的时机错误

IN_CREATE事件触发时,文件仅完成创建动作,但内容可能还未写入完成,甚至创建文件的进程还未关闭文件句柄。此时直接调用copiar_archivo会导致fopen失败,而代码中exit(EXIT_FAILURE)会直接终止整个进程——由于标准输出缓冲未刷新,你看不到错误提示,误以为函数被跳过。

2. 错误处理逻辑过于激进

copiar_archivo中遇到文件打开失败直接调用exit,会导致整个程序终止,而不是让线程处理错误后继续等待下一个事件。

3. 输出缓冲区未强制刷新

printf的输出默认是行缓冲,若没有换行符或手动刷新,进程退出时缓冲区内容可能未被打印,导致你看不到函数执行的提示。

具体修复步骤

(1)修改inotify监听事件类型

将监听掩码从IN_CREATE改为IN_CREATE | IN_CLOSE_WRITE,只在文件写入完成并关闭时(IN_CLOSE_WRITE事件)执行复制操作:

wd = inotify_add_watch(fd, ".", IN_CREATE | IN_CLOSE_WRITE);

(2)调整线程事件处理逻辑

区分IN_CREATE和IN_CLOSE_WRITE事件,仅在文件写入完成后执行复制:

while (i < length) {
    struct inotify_event *event = (struct inotify_event *) &buf[i];
    if (event->mask & IN_CREATE) {
        printf("Hilo %d: archivo '%s' creado\n", num_hilo, event->name);
        fflush(stdout);
    }
    if (event->mask & IN_CLOSE_WRITE) {
        printf("Hilo %d: archivo '%s'写入完成,开始复制\n", num_hilo, event->name);
        fflush(stdout);
        char nom_destino[100] = NOM_FICH;
        copiar_archivo(event->name, nom_destino);
        sleep(1);
        remove(event->name);
    }
    i += EVENT_SIZE + event->len;
}

(3)修复copiar_archivo的错误处理与输出

  • 强制刷新输出缓冲区,确保提示信息能及时显示
  • 将exit改为返回,避免单个线程错误导致整个进程终止:
void copiar_archivo(char *nombre_archivo_x, char *nombre_archivo_y) {
    printf("EY, al menos funciona :)\n");
    fflush(stdout); // 强制刷新缓冲区
    FILE *archivo_x, *archivo_y;
    char buffer[1024];
    size_t n;

    archivo_x = fopen(nombre_archivo_x, "rb");
    if (archivo_x == NULL) {
        perror("Error al abrir archivo X");
        fflush(stderr);
        return; // 返回而非终止进程
    }

    archivo_y = fopen(nombre_archivo_y, "ab");
    if (archivo_y == NULL) {
        perror("Error al abrir archivo Y");
        fflush(stderr);
        fclose(archivo_x);
        return;
    }

    while ((n = fread(buffer, 1, sizeof buffer, archivo_x)) > 0) {
        fwrite(buffer, 1, n, archivo_y);
    }

    if (ferror(archivo_x)) {
        perror("Error al leer archivo X");
        fflush(stderr);
    }

    fclose(archivo_x);
    fclose(archivo_y);
}

内容的提问来源于stack exchange,提问作者Alfonso Moreno

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 22:48:13