sync.once确保操作仅执行一次,通过atomic.compareandswapuint32检查done标志位,首次成功后置1,后续调用直接返回;panic后done仍为1,不重试,需上层处理失败。

并发下未加锁导致多次初始化或 panic
延迟加载最典型的失效场景:多个 goroutine 同时首次调用 getter,sync.Once 没被正确使用,或压根没用——结果是 p.real 被重复赋值、资源被打开多次,甚至触发 nil pointer dereference。
- 错误写法:
if p.real == nil { p.real = &RealLoader{} }—— 这段代码在并发下完全不安全 - 正确做法:把初始化逻辑抽到私有
init()方法里,用p.once.Do(p.init)包裹 - 注意:
init()里别做耗时操作(比如读大文件),否则所有等待的 goroutine 都会被卡住 - 如果初始化可能失败,记得把 error 存到字段里(如
p.initErr),避免下次调用又重试
代理结构体直接嵌入真实实现
想省事把 RealLoader 嵌入 ProxyLoader 结构体?这会让延迟加载彻底失效,且破坏访问控制意图。
- 嵌入后,
ProxyLoader{}初始化时就会执行RealLoader的字段初始化(比如打开文件、连数据库) - 更严重的是,所有未重写的方法会自动透传,暴露本该被拦截的行为(比如你不希望用户调用的
Reset()) - 正确姿势:只持有一个
*RealLoader字段(初始为nil),靠 getter +sync.Once控制创建时机 - 接口要严格定义边界,别让 proxy 实现了它不该有的方法
权限校验写在 proxy 方法里而不是靠接口隔离
在每个方法开头加 if !user.HasPerm("write") { return err } 看似直观,实则埋下长期维护隐患。
- 耦合严重:proxy 不再是单纯的“转发层”,混入业务权限逻辑
- 编译期无保障:用户拿到
AuthProxy实例后,仍可调用Delete()——只是运行时报错 - 推荐做法:拆分接口,比如
Reader和Writer,让不同 proxy 类型只实现对应接口 - 这样调用
Delete()在编译期就报错,而不是等请求打到服务端才拒绝
忘记透传 context 和 defer 生命周期管理
proxy 不是透明壳子,它参与调用链路,必须处理好上下文和资源释放。
- 所有涉及 I/O 的方法(如
Load())应接收context.Context,并在内部透传给真实实现 - 若真实对象持有连接、文件句柄等资源,proxy 必须提供明确的
Close()或类似方法,并确保调用者能触发 -
defer不能写在 proxy 方法里就完事——它只对当前函数生效;真正释放动作得由真实对象自己完成,proxy 只负责转发调用 - 常见坑:proxy 初始化了
*sql.DB,但没暴露出Close(),导致连接泄漏
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











