Bootstrap

Linux 多进程生产者消费者模型实现

生产者消费者模型是并发编程中的经典案例,本文通过一个具体的C++示例,演示如何在Linux环境下使用System V IPC机制实现跨进程的生产者消费者模型。

一、模型核心组件

本实现采用三种关键IPC机制:

  1. 共享内存:存储循环队列数据
  2. 信号量:实现进程间同步
  3. 循环队列:数据缓冲区

二、关键代码解析

1. 信号量封装类(csemp)
class csemp {
public:
    bool init(key_t key, unsigned short value=1, short sem_flg=SEM_UNDO);
    bool wait(short sem_op=-1); // P操作
    bool post(short sem_op=1);  // V操作
    // ...其他成员函数...
};
  • SEM_UNDO标志保证进程异常终止时自动释放信号量
  • 采用RAII模式管理信号量生命周期
2. 共享内存初始化
// 获取/创建共享内存
int shmid = shmget(0x5005, sizeof(squeue<ElemType, 5>), 0640|IPC_CREAT);
// 附加到进程地址空间
squeue<ElemType, 5>* QQ = (squeue<ElemType, 5>*)shmat(shmid, 0, 0);
  • 使用固定key值0x5005标识共享内存
  • 权限设置为0640(用户读写,组读)
3. 生产者核心逻辑
mutex.wait(); // 获取互斥锁
// 数据入队操作
ee.no=3; strcpy(ee.name, "西施"); QQ->push(ee);
// ...其他数据...
mutex.post(); // 释放互斥锁
cond.post(3); // 通知消费者有3个新数据
4. 消费者核心逻辑
while(true) {
    mutex.wait();
    while (QQ->empty()) {
        mutex.post();
        cond.wait();  // 等待数据通知
        mutex.wait();
    }
    // 数据出队处理...
    mutex.post();
}

三、关键同步机制

信号量使用策略
信号量初始值作用标志位
mutex1共享内存访问控制SEM_UNDO
cond0可用数据数量通知0
操作时序图
Producer Consumer Shared Memory mutex cond wait() 写入数据 post() post(N) wait() wait() 读取数据 post() Producer Consumer Shared Memory mutex cond

四、扩展知识

1. System V与POSIX信号量对比
特性System V信号量POSIX信号量
进程间共享原生支持需要命名信号量
原子操作支持批量操作单信号量操作
持久性内核持久需显式删除
初始化灵活性需要额外控制直接初始化
2. 共享内存最佳实践
  1. 始终使用同步机制保护共享内存访问
  2. 初始化时使用双重检查锁模式
  3. 为共享内存设置合理的过期时间
  4. 使用shmdt()及时断开连接
  5. 在程序退出时使用shmctl(IPC_RMID)清理资源
3. 死锁预防策略
  • 按照固定顺序获取锁
  • 设置超时机制
  • 使用SEM_UNDO标志
  • 避免嵌套锁
  • 定期检查系统信号量状态(ipcs命令)

五、性能优化建议

  1. 批量操作优化:适当增大每次生产/消费的数据量
  2. 双缓冲区技术:使用交替缓冲区减少锁竞争
  3. 无锁队列实现:CAS原子操作替代互斥锁
  4. 内存对齐:优化共享内存访问效率
  5. 信号量分组:区分读写信号量提升并发性

六、错误处理建议

// 示例:改进的信号量初始化
bool csemp::init(key_t key, unsigned short value, short sem_flg) {
    if((m_semid = semget(key, 1, 0666)) == -1) {
        if(errno != ENOENT) {
            // 记录详细错误日志
            log_error("Semget failed: %s", strerror(errno));
            return false;
        }
        // 创建新信号量...
    }
    // ...其他初始化逻辑...
}

建议添加:

  1. 详细的错误日志记录
  2. 信号量存在性检测
  3. 自动清理僵尸信号量
  4. 重试机制(ETIMEDOUT处理)

七、完整代码示例

(此处插入用户提供的完整生产者/消费者代码)

八、运行与测试

编译执行命令:

g++ -o producer producer.cpp -lrt -pthread
g++ -o consumer consumer.cpp -lrt -pthread
./producer & ./consumer &

监控系统资源:

watch -n 1 'ipcs -s && ipcs -m'
;