count++不是线程安全的,因其被拆分为读取、加1、写回三步非原子操作,多goroutine并发时会相互覆盖;应改用atomic.addint64(&count, 1)并配合atomic.loadint64读取,或用sync.mutex保护临界区。

为什么 count++ 在 Gin 中不是线程安全的
因为 count++ 不是原子操作:它实际拆成「读取当前值 → 加 1 → 写回内存」三步。当多个 HTTP 请求并发进来(比如用 ab -n 10000 -c 100 压测),多个 goroutine 同时读到同一个旧值,各自加 1 后写回,结果就是覆盖——最终 count 远小于预期值。
atomic.AddInt64 是最轻量的修复方式
适用于单个整数变量的增减,性能最高,且无需锁开销。
- 必须声明为
int64类型(atomic包不支持int直接操作) - 用
atomic.AddInt64(&count, 1)替代count++,返回新值,可直接用于响应 - 读取时用
atomic.LoadInt64(&count),不能直接读变量(可能看到未刷新的缓存值)
sync.Mutex 更通用但需注意 Unlock 时机
适合保护多行逻辑、多个变量或非原子操作组合,但容易因忘记 Unlock() 导致死锁。
- 定义
var mu sync.Mutex实例,和需要保护的变量放在一起 - 所有访问前调用
mu.Lock(),结束后**必须**调用mu.Unlock() - 推荐用
defer mu.Unlock()放在 handler 开头,避免分支遗漏 - 不要在中间件里锁整个请求链——锁粒度太大,会严重拖慢吞吐
别把 context 或全局 map 当“线程安全”用
*gin.Context 是 per-request 的,本身不共享;但往里面塞数据(c.Set("key", val))不解决并发问题。真正危险的是你把计数器、配置缓存、连接池状态之类存在包级变量里,还裸读裸写。
- 包级变量(如
var users map[string]*User)默认无并发保护,读写都要加锁或用sync.Map -
sync.Map适合读多写少场景,但遍历成本高,且不保证顺序,别拿它当普通 map 用 - 数据库连接、HTTP 客户端等资源池对象,本身已内置并发安全机制,直接复用即可,不用额外加锁











