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

C语言信号量与共享内存实现生产者消费者:消费者始终取0

问题分析与修复

你的代码存在三个核心问题,导致消费者总是读到0:

1. 索引变量未放入共享内存

head和tail是main函数的局部变量,fork()后子进程会复制一份独立的副本,生产者和消费者各自维护自己的索引,完全不共享。消费者始终从初始的0位置读取,而生产者写入的是自己副本的索引位置,两者没有交集。

2. 信号量初始值搞反

  • full信号量表示缓冲区中已填充的元素数量,初始时缓冲区为空,应该设为0
  • empty信号量表示缓冲区中空闲槽位的数量,初始时缓冲区全空,应该设为N
    你当前的代码把两者初始值写反了,导致生产者一开始就阻塞在sem_wait(full),无法写入数据。

3. 共享内存结构使用错误

你定义了STRUCT_BUFFER结构体,但代码中直接把共享内存指针ptr当作int数组使用,没有正确访问结构体中的B数组;同时结构体里的head/tail被定义为指针,这完全没必要,应该用整数索引来标记环形缓冲区的位置。


修正后的完整代码

#include <sys/wait.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <semaphore.h>

#define SHM_NAME "/shm8"
#define MUTEX "/mutex"
#define FULL "/full"
#define EMPTY "/empty"

#define N 10
#define SYSC(value, command, message) if ((value=command) == -1){perror(message); exit(errno);}
#define SYSCN(value, command, message) if ((value=command) == NULL){perror(message); exit(errno);}

// 修改结构体:用整数索引代替指针,明确数组字段
typedef struct{
    int B[N];
    int head;  // 读索引
    int tail;  // 写索引
} STRUCT_BUFFER;

void put(int, STRUCT_BUFFER*, sem_t*, sem_t*, sem_t*);
int get(STRUCT_BUFFER*, sem_t*, sem_t*, sem_t*);

int main(){
     
    int ret_value, mem_fd;
    void * ptr;
    pid_t pid;
    sem_t * mutex;
    sem_t * full;
    sem_t * empty;
    STRUCT_BUFFER *buf;

    // 创建共享内存
    SYSC(mem_fd, shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666), "in shm_open");
    SYSC(ret_value, ftruncate(mem_fd, sizeof(STRUCT_BUFFER)), "in ftruncate");

    // 内存映射并转换为结构体指针
    SYSCN(ptr, mmap(NULL, sizeof(STRUCT_BUFFER), PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, 0), "in mmap");
    buf = (STRUCT_BUFFER*)ptr;
    // 初始化共享的索引
    buf->head = 0;
    buf->tail = 0;

    SYSC(ret_value, close(mem_fd), "in close");

    // 创建信号量:修正初始值
    mutex = sem_open(MUTEX, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
    if(mutex == SEM_FAILED) {
        perror("In sem_open");
        exit(EXIT_FAILURE);
    }

    // full初始为0:没有已填充的元素
    full = sem_open(FULL, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 0);
    if(full == SEM_FAILED) {
        perror("In sem_open");
        exit(EXIT_FAILURE);
    }

    // empty初始为N:有N个空闲槽位
    empty = sem_open(EMPTY, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, N);
    if(empty == SEM_FAILED) {
        perror("In sem_open");
        exit(EXIT_FAILURE);
    }

    SYSC(pid, fork(), "in fork");

    if(!pid) {
        // 消费者代码
        printf("Consumer %d starts\n", getpid());

        int toGet;
        while(1) {
            toGet = get(buf, mutex, full, empty);
            printf("Consumer %d consumed %d\n", getpid(), toGet);
            
            // 收到终止标记则退出
            if(toGet == -1) {
                // 注:单消费者场景下无需放回标记,多消费者才需要
                // put(toGet, buf, mutex, full, empty);
                break;
            }
        }

        sleep(1);
        printf("Consumer %d finishes\n",getpid());
        exit(EXIT_SUCCESS);

    } else {
        // 生产者代码
        printf("Producer %d starts\n",getpid());

        int toPut;
        int exitToken = -1;

        // 生产数据
        for(int i = 0 ; i < N ; i++) {
            toPut = i + 1;
            put(toPut, buf, mutex, full, empty);
            printf("Producer %d produced %d. Cycle:%d\n", getpid(), toPut, i);
            sleep(1);            
        }

        // 放入终止标记
        put(exitToken, buf, mutex, full, empty);
        printf("Producer %d produced termination token\n", getpid());
        sleep(1);

        SYSC(ret_value, wait(NULL), "in wait");
        
        // 清理资源
        sem_close(mutex);
        sem_close(empty);
        sem_close(full);

        sem_unlink(MUTEX);
        sem_unlink(FULL);
        sem_unlink(EMPTY);

        munmap(ptr, sizeof(STRUCT_BUFFER));
        shm_unlink(SHM_NAME);  // 别忘了删除共享内存

        printf("Producer %d finishes\n",getpid());
        exit(EXIT_SUCCESS);
    }
}

void put(int toPut, STRUCT_BUFFER *buf, sem_t *mutex, sem_t *full, sem_t *empty) {
    sem_wait(empty);  // 等待空闲槽位
    sem_wait(mutex);

    // 写入共享缓冲区的指定位置
    buf->B[buf->tail] = toPut;
    buf->tail = (buf->tail + 1) % N;

    sem_post(mutex);
    sem_post(full);  // 已填充元素数量+1
}

int get(STRUCT_BUFFER *buf, sem_t *mutex, sem_t *full, sem_t *empty) {
    sem_wait(full);  // 等待有元素可读
    sem_wait(mutex);
    
    // 读取共享缓冲区的指定位置
    int toGet = buf->B[buf->head];
    buf->head = (buf->head + 1) % N;

    sem_post(mutex);
    sem_post(empty);  // 空闲槽位数量+1

    return toGet;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 12:34:57