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

生产者-消费者模型Semaphore异常行为问题排查

生产者-消费者模型同步问题分析

问题现象

  • 初始配置:sem_prod初始值设为1,sem_cons初始值设为0,消费者本应等待生产者生产第一条消息,但启动后直接输出Consumer: Consumed message Message 0
  • 修改配置:将两个信号量初始值均设为0后,预期生产者和消费者会因无法获取信号量而阻塞,但程序仍能正常运行

原代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <string.h>

#define NAME "/my_shm"
#define SIZE 4096

struct shared_data {
    int count;
    char buffer[SIZE];
};

int main(int argc, char *argv[]) {
    int shm_fd;
    struct shared_data *data;
    sem_t *sem_prod, *sem_cons;

    // Create shared memory object
    shm_fd = shm_open(NAME, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(1);
    }

    // Set the size of the shared memory object
    ftruncate(shm_fd, sizeof(struct shared_data));

    // Map the shared memory object to memory
    data = mmap(NULL, sizeof(struct shared_data), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (data == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    // Create semaphores
    sem_prod = sem_open("/sem_prod", O_CREAT, 0666, 1);
    sem_cons = sem_open("/sem_cons", O_CREAT, 0666, 0);

    if (argc == 2 && strcmp(argv[1], "producer") == 0) {
        // Producer
        for (int i = 0; i < 10; i++) {
            sem_wait(sem_prod);

            data->count++;
            snprintf(data->buffer, SIZE, "Message %d", i);
            printf("Producer: Produced message %d\n", i);

            sem_post(sem_cons);
        }
    } else {
        // Consumer
        for (int i = 0; i < 10; i++) {
            sem_wait(sem_cons);

            printf("Consumer: Consumed message %s\n", data->buffer);

            sem_post(sem_prod);
        }
    }

    // Cleanup
    sem_close(sem_prod);
    sem_close(sem_cons);
    munmap(data, sizeof(struct shared_data));
    shm_unlink(NAME);

    return 0;
}

运行结果

Consumer: Consumed message Message 0
Producer: Produced message 1
Consumer: Consumed message Message 1
Producer: Produced message 2
Consumer: Consumed message Message 2
Producer: Produced message 3
Consumer: Consumed message Message 3
Producer: Produced message 4
Consumer: Consumed message Message 4
Producer: Produced message 5
Consumer: Consumed message Message 5
Producer: Produced message 6
Consumer: Consumed message Message 6
Producer: Produced message 7
Consumer: Consumed message Message 7
Producer: Produced message 8
Consumer: Consumed message Message 8
Producer: Produced message 9
Consumer: Consumed message Message 9

问题根源分析

核心:IPC资源未彻底清理

Linux下shm_open创建的共享内存、sem_open创建的命名信号量均为持久化IPC资源,不会随进程退出自动销毁,必须显式调用删除接口才能彻底清除。原代码的错误在于:

  1. 信号量未删除:仅调用sem_close关闭信号量句柄,但未调用sem_unlink删除系统中的信号量实体,导致信号量会一直留存,下次启动程序时sem_open的O_CREAT选项会直接复用旧信号量,完全忽略你设置的初始值。
  2. 共享内存残留数据:虽然调用了shm_unlink,但如果消费者先退出执行该操作,生产者后续写入的数据仍会残留在内存中;且如果未彻底删除共享内存,重启程序会直接复用旧内存内容。

对应问题1的具体原因

第一次运行生产者后,共享内存中已写入Message 0,且sem_cons的信号量计数因之前的10次sem_post残留为10。重启消费者时:

  • sem_open复用旧的sem_cons(计数不为0),sem_wait(sem_cons)直接通过
  • 读取到共享内存中残留的Message 0并输出

对应问题2的具体原因

同样是复用了之前运行残留的信号量计数,旧信号量的数值并非0,因此生产者和消费者都能正常获取信号量推进执行。

修复方案

  1. 正确清理IPC资源:
    • 信号量使用完毕后,调用sem_unlink删除信号量实体,建议仅由生产者进程执行一次(避免重复删除报错)
    • 共享内存的shm_unlink也仅由生产者执行,确保资源彻底清理
  2. 初始化共享内存:生产者启动时清空共享内存内容,避免残留旧数据
  3. 强制创建新IPC资源:使用sem_open的O_EXCL选项,确保每次启动都创建新信号量,若旧资源存在则先删除再重建

修改后的关键代码片段

// 处理信号量:若已存在则删除后重建
sem_prod = sem_open("/sem_prod", O_CREAT | O_EXCL, 0666, 1);
if (sem_prod == SEM_FAILED) {
    sem_unlink("/sem_prod");
    sem_prod = sem_open("/sem_prod", O_CREAT | O_EXCL, 0666, 1);
}

sem_cons = sem_open("/sem_cons", O_CREAT | O_EXCL, 0666, 0);
if (sem_cons == SEM_FAILED) {
    sem_unlink("/sem_cons");
    sem_cons = sem_open("/sem_cons", O_CREAT | O_EXCL, 0666, 0);
}

// 生产者初始化共享内存
if (argc == 2 && strcmp(argv[1], "producer") == 0) {
    data->count = 0;
    memset(data->buffer, 0, SIZE);
    // ... 原生产者逻辑
}

// 仅生产者执行最终清理
if (argc == 2 && strcmp(argv[1], "producer") == 0) {
    sem_unlink("/sem_prod");
    sem_unlink("/sem_cons");
    shm_unlink(NAME);
}

手动清理残留IPC资源命令

  • 查看命名信号量:ls /dev/shm/sem.*
  • 删除信号量:rm /dev/shm/sem.sem_prod /dev/shm/sem.sem_cons
  • 查看共享内存:ipcs -m
  • 删除共享内存:ipcrm -m <shmid>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:50:09