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

使用POSIX命名信号量同步父子进程打印文件出现未定义行为的问题

问题分析与修正:POSIX命名信号量同步父子进程打印奇偶

我需要实现父进程打印1-99的奇数、子进程打印偶数到文本文件的功能,用POSIX命名信号量同步,但出现未定义行为。原代码如下:

原主函数代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/types.h>
#include<fcntl.h>
FILE * ptr;     //file pointer

sem_t *sem_even,*sem_odd; //semaphores

void fileWR();  

int main(void){

sem_even=sem_open("/S_even",O_CREAT,0644,0); //initiliaze sem _even to 0
sem_odd=sem_open("/S_odd",O_CREAT,0644,1); //initialize se_odd to 1

fileWR();
return 0;
}

原fileWR函数代码

void fileWR(){

    ptr=fopen("./TpSemaphore.txt","w+"); //open file in read write mode
    
    if(ptr==NULL){
        fprintf(stderr,"ERROR!");
        exit(-1);
            }
    if(fork()!=0){
        //Parent processs
        puts("#### Parent #################");
        int i=1;
        
        while(i<100){
                    
        if(i%2==0) {  //Critical section
        
            sem_wait(sem_odd);
            //print in file
            fprintf(ptr,"%d ",i);
            //visualize which value is being printed
            printf("%d ",i);
            
            sem_post(sem_even);
                    }
            i++;    
            
        }
        sem_unlink("S_odd"); //unlink semphore
        sem_close(sem_odd); //close semaphore
        fclose(ptr);    //close file
        exit(0);
    }
    else{
        //Child process
    
        puts("#### Child #################");
        int j=1;
        while(j<100){
            
        if(j%2!=0) {
            sem_wait(sem_even);
            //print in file
            fprintf(ptr,"%d ",j);
            
            printf("%d ",j);
            
            sem_post(sem_odd);
                        }
            j++;    
        }
        
        sem_unlink("S_even");
        sem_close(sem_even);
        fclose(ptr);
        exit(0);
    }

}

问题根源

1. 核心逻辑完全颠倒

父进程本该处理奇数,代码里却只在i%2==0(偶数)时执行;子进程本该处理偶数,却只在j%2!=0(奇数)时执行,职责完全搞反。

2. 信号量同步逻辑错误

初始sem_odd设为1是想让奇数进程先执行,但现在父进程(错误处理偶数)在等sem_odd,子进程(错误处理奇数)在等初始为0的sem_even,导致子进程一开始就阻塞,父进程跳到偶数才执行,顺序彻底混乱。

3. 文件缓冲导致输出乱序

fork后父子进程各自持有独立的FILE结构体,用户态缓冲会让输出内容无法实时写入磁盘,最终文件内容乱序甚至重复。

4. 信号量操作不规范

  • sem_unlink路径错误:sem_open用的是"/S_odd",但sem_unlink写的是"S_odd",少了根路径前缀,无法正确删除信号量,会导致下次运行残留旧信号量。
  • 信号量unlink时机错误:父进程刚关闭sem_odd就unlink,子进程可能还在使用该信号量,触发未定义行为。

不需要共享内存

这里只是同步对文件的写入操作,文件本身是内核维护的共享资源,不需要额外使用共享内存。


修正后的代码

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

sem_t *sem_even, *sem_odd;

void fileWR();

int main(void) {
    // 用O_EXCL确保信号量是新创建的,避免残留旧信号量
    sem_even = sem_open("/S_even", O_CREAT | O_EXCL, 0644, 0);
    sem_odd = sem_open("/S_odd", O_CREAT | O_EXCL, 0644, 1);

    // 如果信号量已存在,先删除再重试
    if (sem_even == SEM_FAILED || sem_odd == SEM_FAILED) {
        sem_unlink("/S_even");
        sem_unlink("/S_odd");
        sem_even = sem_open("/S_even", O_CREAT | O_EXCL, 0644, 0);
        sem_odd = sem_open("/S_odd", O_CREAT | O_EXCL, 0644, 1);
        if (sem_even == SEM_FAILED || sem_odd == SEM_FAILED) {
            perror("sem_open failed");
            exit(EXIT_FAILURE);
        }
    }

    fileWR();

    // 等待子进程完全结束
    wait(NULL);

    // 所有进程关闭后再删除信号量
    sem_close(sem_odd);
    sem_close(sem_even);
    sem_unlink("/S_odd");
    sem_unlink("/S_even");

    return 0;
}

void fileWR() {
    // 用系统调用open/write绕过用户态缓冲,避免乱序
    int fd = open("./TpSemaphore.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open file failed");
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();
    if (pid > 0) {
        // 父进程:打印1-99的奇数
        puts("#### Parent (Odd) #################");
        for (int i = 1; i < 100; i += 2) {
            sem_wait(sem_odd);

            char buf[10];
            int len = snprintf(buf, sizeof(buf), "%d ", i);
            write(fd, buf, len);
            printf("%d ", i);

            sem_post(sem_even);
        }
        close(fd);
        exit(EXIT_SUCCESS);
    } else if (pid == 0) {
        // 子进程:打印1-99的偶数
        puts("#### Child (Even) #################");
        for (int j = 2; j < 100; j += 2) {
            sem_wait(sem_even);

            char buf[10];
            int len = snprintf(buf, sizeof(buf), "%d ", j);
            write(fd, buf, len);
            printf("%d ", j);

            sem_post(sem_odd);
        }
        close(fd);
        exit(EXIT_SUCCESS);
    } else {
        perror("fork failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
}

修正要点说明

  1. 修正职责逻辑:父进程循环处理奇数(步长2),子进程处理偶数(步长2),去掉多余条件判断,逻辑更清晰。
  2. 修复信号量同步:父进程等待sem_odd(初始1,直接执行),打印后释放sem_even;子进程等待sem_even,打印后释放sem_odd,形成交替执行的同步链。
  3. 解决文件缓冲问题:改用open+write系统调用绕过用户态缓冲;如果坚持用FILE*,可在fopen后调用setbuf(ptr, NULL)禁用缓冲。
  4. 规范信号量操作:用O_EXCL确保信号量全新创建,主进程等待子进程结束后统一关闭、删除信号量。
  5. 添加错误处理:增加sem_open、fork、open的错误检查,避免静默失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 23:50:40