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

使用二元信号量的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:35:36