题目
设计一个生产者-消费者模型,使用共享内存和信号量进行进程间通信
信息
- 类型:问答
- 难度:⭐⭐
考点
共享内存,信号量,进程同步,资源管理
快速回答
实现要点:
- 使用
shmget()/mmap()创建共享内存存储循环缓冲区 - 定义三个信号量:
mutex:二进制信号量保证缓冲区操作原子性empty:计数信号量跟踪空槽位full:计数信号量跟踪已用槽位
- 生产者流程:
- 等待
empty信号量 - 获取
mutex - 写入数据到缓冲区
- 释放
mutex - 增加
full信号量
- 等待
- 消费者流程:
- 等待
full信号量 - 获取
mutex - 读取缓冲区数据
- 释放
mutex - 增加
empty信号量
- 等待
原理说明
生产者-消费者模型是经典的进程同步问题,需解决:
- 资源共享:通过共享内存实现高效数据交换
- 同步机制:信号量保证操作顺序:
mutex:确保同一时刻只有一个进程访问缓冲区(互斥锁)empty:初始值为缓冲区大小N,生产者等待full:初始值为0,消费者等待
- 缓冲区设计:循环队列结构(FIFO)
代码示例(C语言)
#include <sys/shm.h>
#include <semaphore.h>
// 共享内存结构定义
typedef struct {
int buffer[10];
int in, out;
} shm_struct;
// 主流程伪代码
int main() {
// 1. 创建共享内存
int shm_id = shmget(IPC_PRIVATE, sizeof(shm_struct), 0666);
shm_struct *shm_ptr = shmat(shm_id, NULL, 0);
// 2. 初始化信号量
sem_t *mutex = sem_open("/mutex_sem", O_CREAT, 0666, 1);
sem_t *empty = sem_open("/empty_sem", O_CREAT, 0666, 10); // 缓冲区大小10
sem_t *full = sem_open("/full_sem", O_CREAT, 0666, 0);
// 3. 生产者进程
if (fork() == 0) {
int item;
while (1) {
item = produce_item(); // 生产数据
sem_wait(empty); // 等待空槽位
sem_wait(mutex); // 进入临界区
shm_ptr->buffer[shm_ptr->in] = item;
shm_ptr->in = (shm_ptr->in + 1) % 10;
sem_post(mutex); // 离开临界区
sem_post(full); // 增加已用槽位
}
}
// 4. 消费者进程
if (fork() == 0) {
int item;
while (1) {
sem_wait(full); // 等待数据
sem_wait(mutex); // 进入临界区
item = shm_ptr->buffer[shm_ptr->out];
shm_ptr->out = (shm_ptr->out + 1) % 10;
sem_post(mutex); // 离开临界区
sem_post(empty); // 增加空槽位
consume_item(item); // 消费数据
}
}
}最佳实践
- 信号量初始化:确保
empty初始值=缓冲区大小,full初始值=0 - 错误处理:检查所有系统调用返回值(如
sem_wait可能被信号中断) - 资源清理:进程退出时:
- 调用
sem_unlink()删除命名信号量 - 用
shmctl(shm_id, IPC_RMID, 0)释放共享内存
- 调用
- 性能优化:使用POSIX无名信号量(
sem_init)避免文件系统操作
常见错误
- 死锁风险:
- 错误顺序获取信号量(如先
wait(mutex)再wait(empty)) - 忘记释放信号量
- 错误顺序获取信号量(如先
- 竞态条件:未用
mutex保护in/out指针更新 - 内存泄漏:未正确释放共享内存和信号量
- 缓冲区溢出:循环队列索引计算错误(必须取模)
扩展知识
- 信号量vs互斥锁:信号量可计数,互斥锁本质是二进制信号量
- 替代方案:
- 管道/FIFO:适用于流式数据但速度较慢
- 消息队列:结构化数据但需要拷贝开销
- 多生产者优化:使用原子操作(如CAS)更新索引减少锁竞争
- 现代IPC:Linux eventfd 或 kqueue 实现更高性能同步