生产者-消费者模型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资源,不会随进程退出自动销毁,必须显式调用删除接口才能彻底清除。原代码的错误在于:
- 信号量未删除:仅调用
sem_close关闭信号量句柄,但未调用sem_unlink删除系统中的信号量实体,导致信号量会一直留存,下次启动程序时sem_open的O_CREAT选项会直接复用旧信号量,完全忽略你设置的初始值。 - 共享内存残留数据:虽然调用了
shm_unlink,但如果消费者先退出执行该操作,生产者后续写入的数据仍会残留在内存中;且如果未彻底删除共享内存,重启程序会直接复用旧内存内容。
对应问题1的具体原因
第一次运行生产者后,共享内存中已写入Message 0,且sem_cons的信号量计数因之前的10次sem_post残留为10。重启消费者时:
sem_open复用旧的sem_cons(计数不为0),sem_wait(sem_cons)直接通过- 读取到共享内存中残留的
Message 0并输出
对应问题2的具体原因
同样是复用了之前运行残留的信号量计数,旧信号量的数值并非0,因此生产者和消费者都能正常获取信号量推进执行。
修复方案
- 正确清理IPC资源:
- 信号量使用完毕后,调用
sem_unlink删除信号量实体,建议仅由生产者进程执行一次(避免重复删除报错) - 共享内存的
shm_unlink也仅由生产者执行,确保资源彻底清理
- 信号量使用完毕后,调用
- 初始化共享内存:生产者启动时清空共享内存内容,避免残留旧数据
- 强制创建新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
相关产品推荐
相关产品推荐

