使用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); } }
修正要点说明
- 修正职责逻辑:父进程循环处理奇数(步长2),子进程处理偶数(步长2),去掉多余条件判断,逻辑更清晰。
- 修复信号量同步:父进程等待
sem_odd(初始1,直接执行),打印后释放sem_even;子进程等待sem_even,打印后释放sem_odd,形成交替执行的同步链。 - 解决文件缓冲问题:改用
open+write系统调用绕过用户态缓冲;如果坚持用FILE*,可在fopen后调用setbuf(ptr, NULL)禁用缓冲。 - 规范信号量操作:用
O_EXCL确保信号量全新创建,主进程等待子进程结束后统一关闭、删除信号量。 - 添加错误处理:增加
sem_open、fork、open的错误检查,避免静默失败。
内容的提问来源于stack exchange,提问作者ya22y
相关产品推荐
相关产品推荐

