Linux 共享内存 + 信号量实现经典生产者-消费者模型

张开发
2026/4/13 9:45:10 15 分钟阅读

分享文章

Linux 共享内存 + 信号量实现经典生产者-消费者模型
一、前言在多进程编程中共享内存是最快的进程间通信方式但它本身不提供同步机制容易出现数据竞争和脏读问题。信号量Semaphore则是解决同步问题的经典工具。本文将通过一个经典的生产者-消费者模型演示如何使用System V 共享内存shmSystem V 信号量sem实现两个进程之间的安全数据传递。生产者Producer从键盘读取字符串写入共享内存。消费者Consumer从共享内存读取数据并打印输出。通过两个信号量实现互斥与同步保证数据不会丢失、不会重复、不会覆盖。二、核心设计思路我们使用两个信号量来控制生产和消费节奏信号量初始值含义作用SEM11缓冲区空闲可写生产者使用SEM20缓冲区有数据可读消费者使用同步规则生产者P(SEM1) → 写入共享内存 → V(SEM2)消费者P(SEM2) → 读取并打印 → V(SEM1)这样就完美实现了一生产一消费的同步关系同时避免了忙等待。三、完整代码实现1. 信号量封装头文件 sem.hC// sem.h #ifndef SEM_H #define SEM_H #include sys/sem.h enum INDEX { SEM1 0, SEM2 }; union semun { int val; }; void sem_init(); void sem_p(int index); void sem_v(int index); void sem_destroy(); #endif2. 信号量实现 sem.cC// sem.c #include sem.h #include stdio.h #include sys/sem.h static int semid -1; void sem_init() { semid semget((key_t)1234, 2, IPC_CREAT | IPC_EXCL | 0600); if (semid -1) { semid semget((key_t)1234, 2, 0600); if (semid -1) { perror(semget err); return; } } else { // 初始化两个信号量SEM11可写SEM20不可读 int arr[2] {1, 0}; for (int i 0; i 2; i) { union semun a; a.val arr[i]; if (semctl(semid, i, SETVAL, a) -1) { perror(semctl init err); } } } } void sem_p(int index) { struct sembuf buf {index, -1, SEM_UNDO}; if (semop(semid, buf, 1) -1) { perror(semop P err); } } void sem_v(int index) { struct sembuf buf {index, 1, SEM_UNDO}; if (semop(semid, buf, 1) -1) { perror(semop V err); } } void sem_destroy() { if (semctl(semid, 0, IPC_RMID) -1) { perror(semctl destroy err); } }3. 生产者进程producer.cC// producer.c #include stdio.h #include stdlib.h #include unistd.h #include string.h #include sys/shm.h #include sem.h int main() { // 创建/获取共享内存 int shmid shmget((key_t)2345, 128, IPC_CREAT | 0600); if (shmid -1) exit(1); char *s (char *)shmat(shmid, NULL, 0); if (s (char *)-1) exit(1); sem_init(); printf( 生产者启动 \n); while (1) { printf(请输入要发送的内容输入 end 结束\n); char buff[128] {0}; fgets(buff, 128, stdin); sem_p(SEM1); // 等待缓冲区空闲 strcpy(s, buff); sem_v(SEM2); // 通知消费者有数据 if (strncmp(buff, end, 3) 0) { break; } } shmdt(s); return 0; }4. 消费者进程consumer.cC// consumer.c #include stdio.h #include stdlib.h #include unistd.h #include string.h #include sys/shm.h #include sem.h int main() { int shmid shmget((key_t)2345, 128, IPC_CREAT | 0600); if (shmid -1) exit(1); char *s (char *)shmat(shmid, NULL, 0); if (s (char *)-1) exit(1); sem_init(); printf( 消费者启动 \n); while (1) { sem_p(SEM2); // 等待有数据可读 if (strncmp(s, end, 3) 0) { break; } printf(收到消息: %s, s); sem_v(SEM1); // 通知生产者可以继续写入 } shmdt(s); sem_destroy(); // 消费者负责清理信号量和共享内存 shmctl(shmid, IPC_RMID, NULL); // 删除共享内存推荐添加 printf( 消费者已退出 \n); return 0; }四、编译与运行Bashgcc -o sem.o sem.c -c gcc -o producer producer.c sem.o gcc -o consumer consumer.c sem.o # 推荐先启动消费者再启动生产者 ./consumer ./producer运行效果 在生产者中输入内容后消费者会立即打印出来输入 end 后两个进程都会优雅退出。五、关键知识点总结两个信号量协同工作一个控制“可生产”一个控制“可消费”经典的生产者-消费者同步模式。SEM_UNDO防止进程异常退出导致死锁提高程序健壮性。共享内存清理建议在消费者中调用 shmctl(shmid, IPC_RMID, NULL) 删除共享内存避免残留。fgets()比 gets() 安全能保留换行符实际使用中可根据需要去掉 \n。

更多文章