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

多进程并发写入文件异常:程序执行结果不符预期的问题排查

多进程并发写入文件异常排查

需求与问题现象

需要实现一个包含1个主进程+9个子进程的程序,并发写入output.txt文件:

  • 主进程写入5个0:"00000"
  • 第1个子进程写入5个1:"11111"
  • 第2个子进程写入5个2:"22222"
  • ...以此类推,最终文件内容应为00000111112222233333444445555566666777778888899999

但连续执行10次后,结果频繁出现数字缺失、顺序混乱的情况。

原程序代码(numbers.c)

int main(void)
{
    int fd1,fd2,i,pos;
    char c;
    char buffer[6];

    fd1 = open("output.txt", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
    write(fd1, "00000", 5);
    for (i=1; i < 10; i++) {
        pos = lseek(fd1, 0, SEEK_CUR);
        if (fork() == 0) {
            /* Child */
            sprintf(buffer, "%d", i*11111);
            lseek(fd1, pos, SEEK_SET);
            write(fd1, buffer, 5);
            close(fd1);
            exit(0);
        } else {
            /* Parent */
            lseek(fd1, 5, SEEK_CUR);
        }
    }

    //wait for all childs to finish
    while (wait(NULL) != -1);

    lseek(fd1, 0, SEEK_SET);
    printf("File contents are:\n");
    while (read(fd1, &c, 1) > 0)
        printf("%c", (char) c);
    printf("\n");
    close(fd1);
    exit(0);
}

10次执行结果

$ for i in $(seq 10); do ./numbers ; done
File contents are:
0000011111222223333355555666668888899999
File contents are:
00000111112222255555666668888899999
File contents are:
0000011111222223333355555666668888899999
File contents are:
00000111112222244444666667777799999
File contents are:
00000444447777755555666668888899999
File contents are:
00000222224444455555777778888899999
File contents are:
0000011111222224444455555777778888899999
File contents are:
0000011111222225555544444888887777799999
File contents are:
0000011111222224444455555777778888899999
File contents are:
0000011111222225555544444888887777799999

代码中的核心问题

1. 多进程共享文件偏移量的竞态条件

fork()后,子进程会复制父进程的文件描述符,但文件偏移量是内核中打开文件对象的公共属性,并非进程私有。父进程和所有子进程的文件描述符指向同一个内核文件对象,共享同一个偏移量:

  • 父进程在循环中先获取当前偏移量pos,随后fork创建子进程,紧接着就执行lseek(fd1,5,SEEK_CUR)修改偏移量,此时子进程还未执行自己的lseek,导致子进程后续要使用的pos对应的偏移量已经被篡改。
  • 多个子进程之间也会互相干扰:比如子进程A执行lseek设置好写入位置后,CPU切换到子进程B执行lseek修改偏移量,当CPU切回子进程A时,write会从子进程B设置的偏移量开始写入,完全偏离目标位置。

2. lseek与write操作非原子性

子进程中lseek定位位置和write写入的两步操作不是原子的,中间可能被其他进程的操作打断,导致写入位置错误、内容被覆盖。

3. 无进程同步机制

多个进程并发操作文件时,没有使用锁机制保证各自写入操作的独立性,进程间的操作完全无序,进一步加剧了内容混乱的问题。

修复方案

方案1:固定写入位置+记录锁

每个进程直接计算自己的固定写入起始位置(主进程写0-4,子进程i写i*5到i*5+4),同时使用fcntl记录锁保证lseek+write操作的原子性:

#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    int fd1,i;
    char c;
    char buffer[6];

    fd1 = open("output.txt", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
    // 主进程写入固定位置0-4
    lseek(fd1, 0, SEEK_SET);
    write(fd1, "00000", 5);
    
    for (i=1; i < 10; i++) {
        if (fork() == 0) {
            /* Child */
            sprintf(buffer, "%d", i*11111);
            off_t target_pos = i * 5;
            
            // 申请写入区域的独占锁
            struct flock lock = {
                .l_type = F_WRLCK,
                .l_whence = SEEK_SET,
                .l_start = target_pos,
                .l_len = 5,
                .l_pid = getpid()
            };
            fcntl(fd1, F_SETLKW, &lock); // 阻塞等待锁
            
            lseek(fd1, target_pos, SEEK_SET);
            write(fd1, buffer, 5);
            
            // 释放锁
            lock.l_type = F_UNLCK;
            fcntl(fd1, F_SETLK, &lock);
            
            close(fd1);
            exit(0);
        }
    }

    // 等待所有子进程完成
    while (wait(NULL) != -1);

    // 打印结果
    lseek(fd1, 0, SEEK_SET);
    printf("File contents are:\n");
    while (read(fd1, &c, 1) > 0)
        printf("%c", (char) c);
    printf("\n");
    close(fd1);
    exit(0);
}

方案2:父进程预分配位置(无需锁)

由于每个进程的写入区域完全不重叠,父进程可以提前计算好每个子进程的写入位置,在fork前传递给子进程,子进程直接使用固定位置写入,无需锁机制即可避免冲突。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 17:05:25