框架对象与并发安全注意事项
面试问法:GoFrame 里哪些对象并发安全?多个 goroutine 共用 dao/缓存会不会出问题? 一句话结论:要区分“别名容器”与“安全容器”:
g.Map、g.List、g.Slice只是原生类型的别名,并发写不安全;需要并发安全时用gmap/garray/glist/gset/gtype及gcache/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 新起 |
实践纪律
- 请求内共享数据走 ctx:
SetCtxVar/GetCtxVar,不要在全局 map 里按请求写。 - 动态配置/开关用
gvar.Var或gcache原子更新,别裸写共享变量。 - Model 链带状态(where/order 累积),跨 goroutine 复用一个 Model 对象有风险——每个请求从
dao.Xxx.Ctx(ctx)现取。 - 单例初始化用包级 var + sync.Once 或框架单例;避免 init 里做依赖网络的初始化。
- 业务 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 别跨生命周期。
- 各容器默认值与开关方式以官方文档为准。