I/O Completion Ports多线程设计中何时增减Per Socket引用计数?
IOCP中CompletionKey(Socket结构)引用计数的正确增减时机
嘿,我来帮你捋捋这个问题——IOCP里的引用计数确实是个容易踩坑的点,我之前在做高并发网络服务的时候也在这上面折腾过好一阵子。结合你的情况,我把正确的增减时机拆成两个部分说:
什么时候增加引用计数?
- 初始化阶段:当你刚创建Socket并生成对应的CompletionKey结构,调用
CreateIoCompletionPort把Socket和IOCP端口绑定的时候,要把RefCount初始化为1——这时候IOCP和你的业务逻辑都已经“持有”这个结构了。 - 发起异步I/O请求前:你现在做的在
WSARecv/WSASend/AcceptEx这类异步调用前加引用是完全正确的!因为每个异步请求都会把CompletionKey挂到IOCP的等待队列里,只要请求没完成,这个结构就绝对不能被销毁。 - 业务线程持有指针时:如果你的代码里有其他线程(比如专门的Socket管理线程)需要遍历或者操作这个CompletionKey指针,在获取指针的那一刻就要加引用,操作完成后再减——避免操作到一半结构被释放。
什么时候减少引用计数?
- 异步I/O请求完成后:在
GetQueuedCompletionStatus的处理逻辑里,不管这个I/O是成功、失败还是被取消,只要请求已经完成,处理完结果后就必须减引用计数。这里是最容易遗漏的点,很多内存泄漏或者野指针问题都出在这儿。 - 主动关闭Socket时:当你决定要关闭某个Socket时,不能直接释放CompletionKey!要先停止发起新的I/O请求,然后等所有未完成的I/O都处理完毕(也就是对应的引用都减完),当RefCount降到0的时候,再销毁Socket和CompletionKey结构。如果需要快速触发,可以投递一个空的I/O请求,确保IOCP能收到最后一个完成通知来完成计数递减。
- 解绑IOCP关联时:如果你的业务逻辑需要把Socket从IOCP上解绑(调用
CreateIoCompletionPort传入其他端口或者NULL),这时候也要减一次引用——因为IOCP不再持有这个结构的引用了。 - 业务操作完成后:之前提到的业务线程如果加过引用,操作结束后一定要记得减计数,比如遍历完Socket列表、完成某个业务回调之后。
关键提醒!
所有的引用计数操作必须是线程安全的!一定要用Windows提供的原子操作函数:InterlockedIncrement和InterlockedDecrement,绝对不能用普通的++/--——多线程环境下普通加减会导致计数混乱,轻则内存泄漏,重则程序崩溃。
举个简单的完整流程例子:
- 创建Socket → 初始化CompletionKey,
RefCount = 1→ 绑定到IOCP - 准备接收数据:
InterlockedIncrement(&key->RefCount)→ 调用WSARecv - IOCP线程收到完成通知:处理接收的数据 →
InterlockedDecrement(&key->RefCount)→ 检查如果RefCount == 0,就释放CompletionKey和Socket - 主动关闭Socket:调用
closesocket→ 停止发起新的I/O请求 → 等待所有未完成I/O处理完毕,RefCount归0后释放资源
内容的提问来源于stack exchange,提问作者LYC
相关产品推荐
相关产品推荐

