基于共享内存/信号量的消息队列如何检测无进程连接
核心结构定义
现有消息队列结构定义如下:
typedef struct { char data[MESSAGE_SIZE]; } MESSAGE; typedef struct { sem_t sem_send; sem_t sem_receive; MESSAGE messages[]; } MQ;
要实现删除队列时的存活连接检测,不需要依赖特殊内核接口,直接在共享内存中实现跨进程引用计数+删除标记即可,逻辑稳定且无额外性能开销,具体方案如下:
实现逻辑
扩展共享内存控制头
在MQ结构体中新增3个存放在共享内存区域的字段,保证所有连接进程可见:sem_lock:控制互斥信号量,初始值为1,专门用来保护引用计数、删除标记的并发读写,避免多进程同时修改导致数据错乱ref_cnt:跨进程原子引用计数,记录当前已连接的进程总数。优先用C11标准的atomic_int类型,旧编译环境可以用普通int搭配GCC__atomic系列原子内置函数操作delete_pending:待删除标记,用int类型即可,0表示队列正常运行,1表示队列已进入删除流程,禁止新连接接入
修改后的结构参考:
typedef struct { char data[MESSAGE_SIZE]; } MESSAGE; typedef struct { sem_t sem_lock; sem_t sem_send; sem_t sem_receive; atomic_int ref_cnt; int delete_pending; MESSAGE messages[]; } MQ;初始化队列时注意,
sem_init的第二个参数pshared必须传1(标记信号量为跨进程共享),ref_cnt初始值设为0,delete_pending初始值设为0。统一连接准入逻辑
所有进程调用shmat挂载共享内存成功后,必须先走完以下校验才能开始收发消息:- 等待
sem_lock进入临界区 - 检查
delete_pending值,如果为1,说明队列已经在删除流程中,立刻释放sem_lock、执行shmdt断开挂载,返回连接失败 - 标记为0的话,对
ref_cnt做原子加1 - 释放
sem_lock,正式完成连接建立。
- 等待
统一断连逻辑
所有进程结束队列使用、主动断开时,按以下流程执行:- 等待
sem_lock进入临界区 - 对
ref_cnt做原子减1 - 如果减完后
ref_cnt为0,且delete_pending为1,说明当前是最后一个存活连接,直接在当前进程中执行资源回收:依次sem_destroy销毁所有信号量(sem_lock/sem_send/sem_receive)、调用shm_unlink解绑共享内存 - 释放
sem_lock,执行shmdt断开自身的共享内存挂载即可。
- 等待
统一删除入口逻辑
业务侧调用删除队列接口时,不需要等待进程退出,也不需要主动遍历系统进程,只需要打标记:- 等待
sem_lock进入临界区 - 将
delete_pending设为1 - 检查当前
ref_cnt值,如果已经是0,说明当前没有任何存活连接,直接在当前调用流程里完成信号量销毁、shm_unlink操作 - 释放
sem_lock,执行shmdt断开自身挂载后直接返回即可。如果当前ref_cnt大于0,不需要阻塞等待,最后一个进程断开时会自动触发资源回收,不会残留泄漏。
- 等待
踩坑提醒
- 所有对
ref_cnt、delete_pending的读写必须在sem_lock保护的临界区内执行,或者用严格的跨进程原子操作,绝对不能裸读裸写,否则会出现计数不准、重复销毁、内存泄漏问题 - 所有信号量必须分配在共享内存区域,初始化时必须设置跨进程共享属性,否则多进程拿到的是独立信号量,完全起不到同步作用
- 待删除标记设置后必须拦截所有新连接请求,避免资源已经销毁还有新进程挂载访问,触发野指针崩溃
内容的提问来源于stack exchange,提问作者Satcha
相关产品推荐
相关产品推荐

