用 sync.Map 存用户连接更安全,因其专为高并发读多写少设计,Store/Delete/Range 均线程安全;需配合原子计数器统计人数并实现心跳保活防假在线。

用 sync.Map 存用户连接比用普通 map 更安全
HTTP 或 WebSocket 服务中,多个 goroutine 同时增删在线用户时,直接用 map[string]bool 会 panic:"fatal error: concurrent map writes"。Go 官方不保证普通 map 的并发安全。
正确做法是用 sync.Map,它专为高并发读多写少场景设计,底层自动处理锁粒度,不用手动加 sync.RWMutex。
-
sync.Map的Store(key, value)和Delete(key)是线程安全的;Range(f)可安全遍历当前快照 - 不要把它当普通 map 用 —— 没有
len()方法,统计总数得自己计数或用Range累加 - 值类型建议用指针或轻量结构体(如
*UserConn),避免复制开销
WebSocket 连接建立/断开时必须显式更新在线状态
在线人数不是“自动感知”的,得靠业务逻辑在关键节点触发变更。常见漏点是:只在 Upgrade 成功后加,却忘了 defer 断开清理,或没捕获 io.EOF/websocket.CloseMessage 等退出信号。
典型流程:
- 客户端发起 WebSocket 握手 → 服务端用
upgrader.Upgrade()成功 → 调用onlineMap.Store(userID, conn) - 在读循环中监听
conn.ReadMessage(),若返回io.EOF、websocket.ErrCloseSent或其他错误 → 调用onlineMap.Delete(userID) - 主动关闭连接前(如踢人)也要先
Delete,否则下次同 ID 登录会重复计数
别依赖 defer 放在 handler 开头 —— 如果 upgrade 失败,defer 不会执行;更稳妥的是把 Delete 写在读循环末尾或错误处理分支里。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
通过 HTTP 接口暴露实时人数时注意并发读性能
如果每秒有几百次请求查在线人数,每次都 Range 全量 sync.Map 会带来不必要的开销(尤其用户量大时)。这时应该维护一个原子计数器。
- 用
sync/atomic.Int64在每次Store后Add(1),Delete后Add(-1) - HTTP handler 中直接调用
counter.Load(),毫秒级响应,无锁无遍历 - 注意:原子计数器只反映“增删动作次数”,不校验连接是否真实存活;若需强一致性(比如心跳超时自动下线),仍得结合
Range扫描 + 时间戳判断
心跳机制缺失会导致“假在线”堆积
客户端网络断开但没发 close 帧时,服务端 TCP 连接可能长时间处于 ESTABLISHED 状态,sync.Map 里还留着无效连接,人数虚高。
必须实现心跳保活:
- 服务端用
conn.SetPingHandler()接收 ping,自动回 pong - 设置读超时:
conn.SetReadDeadline(time.Now().Add(30 * time.Second)),并在每次读消息前刷新 - 搭配后台 goroutine 定期扫描
sync.Map中连接的最后活动时间,超时则主动conn.Close()并Delete
这个扫描逻辑不能只靠一次定时器 —— 多个 goroutine 并发扫描同一 sync.Map 没问题,但删除前最好再检查一次连接状态,避免竞态下重复 close。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










