使用二元信号量的C语言同步程序出现异常输出问题
二元信号量实现生产者-消费者程序输出异常排查
问题描述
使用二元信号量实现的C语言生产者-消费者同步程序出现输出异常,消费者会输出未输入的残留内容,多次运行异常输出内容不同。
消费者进程代码
#include <stdio.h> #include <sys/sem.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include "sem.h" #define nome_file "consumator_productor.txt" #define DIM 1048 #define SEM_KEY_WRITE (key_t)1234 #define SEM_KEY_READ (key_t)5678 int SEM_P(int); int SEM_V(int); int SEM_DEL(int); int SEM_SET(int, int); int main(void){ int running = 1; int fd, WRITE_ID, READ_ID, c; char buffer[DIM]; //所需变量 if((WRITE_ID = semget(SEM_KEY_WRITE, 1, IPC_CREAT | 0666)) == -1) exit(EXIT_FAILURE); SEM_SET(WRITE_ID, 1); if((READ_ID = semget(SEM_KEY_READ, 1, IPC_CREAT | 0666))== -1) exit(EXIT_FAILURE); SEM_SET(READ_ID, 0); //声明并初始化信号量 while(running){ if(SEM_P(READ_ID) == -1) exit(EXIT_FAILURE); //等待读取许可 fd = open(nome_file, O_RDONLY, 0440); read(fd, buffer, sizeof(buffer)); close(fd); if(strncmp(buffer, "end", 3) == 0){ //检查是否读取到"end" running = 0; } else { printf("%s\n", buffer); } if(SEM_V(WRITE_ID) == -1) exit(EXIT_FAILURE); //释放写入许可 } if(SEM_DEL(WRITE_ID) == -1) exit(EXIT_FAILURE); if(SEM_DEL(READ_ID) == -1) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); }
生产者进程代码
#include <stdio.h> #include <sys/sem.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include "sem.h" #define nome_file "consumator_productor.txt" #define DIM 1048 #define SEM_KEY_WRITE (key_t)1234 #define SEM_KEY_READ (key_t)5678 int SEM_P(int); int SEM_V(int); int SEM_DEL(int); int SEM_SET(int, int); int main(void){ int running = 1; int fd, WRITE_ID, READ_ID; char buffer[DIM]; if((WRITE_ID = semget(SEM_KEY_WRITE, 1, IPC_CREAT | 0666)) == -1) exit(EXIT_FAILURE); if((READ_ID = semget(SEM_KEY_READ, 1, IPC_CREAT | 0666)) == -1) exit(EXIT_FAILURE); while(running){ if(SEM_P(WRITE_ID) == -1) exit(EXIT_FAILURE); //等待写入许可 fd = open(nome_file, O_CREAT | O_TRUNC | O_WRONLY, 0660); printf("Write on your file:\n"); fgets(buffer, sizeof(buffer), stdin); write(fd, buffer, strlen(buffer)); if(strncmp(buffer, "end", 3) == 0){ running = 0; } close(fd); if(SEM_V(READ_ID) == -1) exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
异常输出示例
os@debian:~/Esercizi/semafori/libreria$ ./consumator Ciao, ciao e ciao // 我通过生产者进程输入的内容 // 此行为异常输出,未输入 Mia, mio e miei // 我输入的内容 o // 异常输出 // 异常输出 Ok ora basta. // 我输入的内容 i // 异常输出 o // 异常输出
问题原因分析
1. 缓冲区未正确终止字符串
read()函数仅读取文件内容到buffer中,不会自动在末尾添加字符串终止符\0。如果读取的内容长度小于buffer的大小,buffer中残留的旧数据会被保留,printf("%s")会从当前读取的内容开始,一直输出到遇到\0为止,导致出现残留的异常字符。
修复方案:捕获read()的返回值,手动添加终止符:
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); // 预留一个字节给终止符 if (bytes_read > 0) { buffer[bytes_read] = '\0'; } else if (bytes_read == 0) { buffer[0] = '\0'; // 文件为空时的处理 }
2. 消费者重复初始化信号量
消费者每次启动都会调用SEM_SET(WRITE_ID, 1)和SEM_SET(READ_ID, 0),直接重置信号量的状态。如果生产者已经在运行,这会破坏已有的同步逻辑,导致信号量状态混乱,可能出现消费者提前读取、或者生产者多次写入的情况,进而引发输出异常。
修复方案:仅在信号量首次创建时才进行初始化,可通过semctl()获取信号量状态判断是否为新创建:
// 消费者中初始化信号量的代码修改 int WRITE_ID = semget(SEM_KEY_WRITE, 1, IPC_CREAT | 0666); if (WRITE_ID == -1) exit(EXIT_FAILURE); struct semid_ds sem_info; if (semctl(WRITE_ID, 0, IPC_STAT, &sem_info) == -1) exit(EXIT_FAILURE); // 若信号量是新创建的(otime为0,表明从未被操作过),才初始化 if (sem_info.sem_otime == 0) { SEM_SET(WRITE_ID, 1); } int READ_ID = semget(SEM_KEY_READ, 1, IPC_CREAT | 0666); if (READ_ID == -1) exit(EXIT_FAILURE); if (semctl(READ_ID, 0, IPC_STAT, &sem_info) == -1) exit(EXIT_FAILURE); if (sem_info.sem_otime == 0) { SEM_SET(READ_ID, 0); }
3. 额外注意:信号量删除时机
消费者在退出时删除信号量,但如果生产者仍在运行,会导致后续生产者操作信号量失败。建议单独编写一个清理程序,或者在确认双方都退出后再删除信号量。
内容的提问来源于stack exchange,提问作者Vittorio
相关产品推荐
相关产品推荐

