多线程TCP socket开发:删除socket后何时清理?如何规避竞态条件?
可行解决方案:原子引用计数+销毁标记组合
核心思路是通过引用计数绑定socket结构体(含内嵌mutex)的生命周期,确保只要有线程持有该socket的引用,其内存就不会被释放,从根源上避免访问已释放内存的未定义行为。
结构体定义
你的socket结构体需要包含以下核心成员:
typedef struct socket_s { _Atomic int refcount; // 原子引用计数,初始值为1 pthread_mutex_t mutex; // 直接内嵌在结构体中,无需单独分配 _Atomic bool is_destroyed; // 销毁标记,初始为false int fd; // socket文件描述符 // 其他业务成员:收发缓冲区、会话状态等 } socket_t;
核心执行规则
所有socket操作严格遵循以下逻辑,可完全消除竞态:
- 引用获取规则
任何线程要操作socket前,必须先调用socket_ref()接口:原子检查refcount是否为0,不为0则将refcount+1返回socket指针,为0则返回NULL代表socket已完全释放。
如果你的socket存放在全局连接表/FD映射表中,取socket时需先持有全局表的锁,取到后立即调用socket_ref()加引用,再释放全局表锁。 - 操作前检查规则
拿到有效引用后,先加结构体内嵌的mutex锁,加锁后首先检查is_destroyed标记:- 标记为true:立即解锁,调用
socket_unref()减引用,返回socket已销毁的错误码 - 标记为false:正常执行收发等业务操作,操作完成后解锁,调用
socket_unref()减引用
- 标记为true:立即解锁,调用
- 销毁流程规则
主动/异常需要销毁socket时,按以下顺序执行:- 加
mutex锁,将is_destroyed设为true,关闭fd,释放所有业务资源(缓冲区、会话数据等)后解锁 - 从所有全局索引表(连接表、FD映射表等)中移除该socket的条目,确保后续不会有新线程能拿到该socket的指针调用
socket_ref() - 调用
socket_unref()减去创建socket时的初始引用
- 加
- 引用释放规则
socket_unref()接口原子性将refcount减1,若减完后值为0,说明没有任何线程持有该socket的引用,此时直接销毁内嵌的mutex,释放整个socket结构体的内存即可。
方案优势
完全解决你遇到的核心问题:
- 不存在解锁和释放mutex之间被抢占的问题:
refcount归0前结构体内存始终有效,且全局索引移除后不会有新线程能拿到该socket的引用,最后释放内存时绝对没有线程会再尝试访问mutex - 不存在“检查销毁状态和加锁不原子”的问题:加锁前已经持有有效引用,结构体内存100%有效,加锁后再检查销毁标记逻辑完全安全
如果你不想向上层暴露引用计数接口,可以把ref/unref逻辑完全封装在库的对外接口内部,上层用户只需要传入socket句柄即可,所有生命周期逻辑对上层透明。
内容的提问来源于stack exchange,提问作者user15832549
相关产品推荐
相关产品推荐

