Go语言多函数间如何正确共享单个原子计数器变量
跨方法共享原子计数器实现会话引用计数的正确方案
共享计数器的作用域配置
原子计数器无法在两个方法间访问,核心原因是变量作用域不对,把计数器定义在两个方法都可访问的作用域即可,两种常用实现:
- 如果
createSession、deleteSession是绑定在同一个结构体上的方法,将计数器声明为结构体的顶层字段即可(int32/int64类型的原子字段要放在结构体靠前位置,避免内存对齐问题导致原子操作panic) - 如果两个方法是包级独立函数,直接将计数器声明为包级全局变量即可
示例(结构体方式,也是工程中最常用的实现):
import "sync/atomic" type SessionHandler struct { // 原子计数字段放在结构体最前面,保证内存对齐 refCnt int32 // 其余会话相关字段(比如客户端连接、配置项)正常定义即可 } func (h *SessionHandler) createSession() { // 直接对结构体字段做原子自增 atomic.AddInt32(&h.refCnt, 1) // 原有创建会话的业务逻辑 }
修正原有deleteSession的竞态逻辑
你之前写的CAS校验逻辑存在竞态漏洞:CAS操作失败后直接减计数的逻辑可能导致计数错乱,且CAS后直接读取裸的counter变量不是原子操作,会触发数据竞态。
正确的引用计数逻辑不需要用CAS,直接通过原子自减的返回值判断即可:每次调用deleteSession先原子减1,若减完的结果大于0,说明还有其他执行流持有会话引用,不触发登出;只有减完结果等于0时,才说明当前是最后一个待删除的会话引用,执行实际登出逻辑。
修正后的代码:
func (h *SessionHandler) deleteSession() error { curCnt := atomic.AddInt32(&h.refCnt, -1) if curCnt > 0 { a10log.Info("Don't logoff as session is open") return nil } // 兜底处理重复调用delete导致计数为负的异常场景 if curCnt < 0 { atomic.StoreInt32(&h.refCnt, 0) a10log.Warn("invalid session ref count, reset to 0") return nil } a10log.Info("Logging off") // 此处编写实际调用登出API、删除会话的逻辑 return nil }
注意:所有对
refCnt的读写操作都必须通过sync/atomic包提供的原子方法完成,禁止直接读写裸变量,否则会出现数据竞态,可通过go test -race命令做竞态检测。
内容的提问来源于stack exchange,提问作者Neha Kembalkar
相关产品推荐
相关产品推荐

