框架对象与并发安全注意事项

面试问法:GoFrame 里哪些对象并发安全?多个 goroutine 共用 dao/缓存会不会出问题? 一句话结论:要区分“别名容器”与“安全容器”:g.Mapg.Listg.Slice 只是原生类型的别名,并发写不安全;需要并发安全时用 gmap/garray/glist/gset/gtypegcache/gvar 等容器(默认安全、可关锁提性能,以官方文档为准)。框架单例(g.DB()/g.Cache()/g.Redis())与 Server 自身并发安全,重点管好业务自己写的共享状态。

常见对象安全性速查

对象并发安全说明
g.Map / g.List / g.Slice原生类型别名,只读可共享、并发写要加锁
gmap / garray / glist / gset默认是容器组件,可关闭锁换取性能
gcache.Cache内置锁的内存缓存
gvar.Var可原子替换的变量,适合存动态配置
gtype.*原子类型封装(如 gtype.Int)
g.DB() / g.Redis() 单例内部连接池自管并发
dao 返回的 Model视用法链式状态对象,推荐每请求从 dao 新起

实践纪律

  1. 请求内共享数据走 ctx:SetCtxVar/GetCtxVar,不要在全局 map 里按请求写。
  2. 动态配置/开关用 gvar.Vargcache 原子更新,别裸写共享变量。
  3. Model 链带状态(where/order 累积),跨 goroutine 复用一个 Model 对象有风险——每个请求从 dao.Xxx.Ctx(ctx) 现取。
  4. 单例初始化用包级 var + sync.Once 或框架单例;避免 init 里做依赖网络的初始化。
  5. 业务 goroutine 不要持有 *ghttp.Request 出请求生命周期(对象被框架复用),异步任务只传 ctx 与必要数据。

小示例:共享状态的安全写法

import (
    "github.com/gogf/gf/v2/container/gtype"
    "github.com/gogf/gf/v2/container/gvar"
)

var (
    flagEnabled = gvar.New(true)   // 可原子替换的动态开关
    reqCount    = gtype.NewInt64() // 原子计数器
)

func AdminSwitch(on bool) { flagEnabled.Set(on) } // 任意 goroutine 安全更新

func Inc() int64 { return reqCount.Add(1) }       // 原子自增并返回

注意:这里依赖的是 gvar/gtype 自带的原子与锁;若换成裸 bool/int 共享读写,就必须自己加锁或用 sync/atomic

常见追问

  • 追问:为什么 g.Map 并发写会崩而 gmap 不会?——g.Map 就是内置 map,Go 对 map 并发写直接 fatal;gmap 内部用锁/原子保护。
  • 追问:map 只是并发读安全吗?——并发读写同一 map 也不安全(写时可能触发运行时 panic),读多写少也要用同步原语或换并发安全容器。

记忆点

  • 别名容器裸 map/slice 非安全;官方容器默认安全可关锁;单例框架对象随便复用,Model/Request 别跨生命周期。
  • 各容器默认值与开关方式以官方文档为准。
笔记加载中…