最近 Go 又有了新的动作,一路绿灯,已经审批通过了新增弱指针(weak pointers)库的提案,这对于 Go 来讲是一个不错的补充。
今天这篇文章我们主要分享弱指针的介绍,让大家有一个基本前瞻。因为这个提案已经 Accepted!
弱指针是什么
此处是引用 Go 官方提案给出的介绍和原因,也就是为什么 Go 核心团队认可要去做弱指针。
弱指针(或其他语言中的弱引用)允许开发人员引用内存,而不妨碍垃圾回收器回收内存。为防止出现可见的悬挂引用,弱指针在引用的内存被回收时会变为零。
弱指针可以转换为常规(“强”)指针,这样就能防止垃圾回收器回收内存,并允许对内存进行典型使用和访问。
弱指针通常比普通指针更难处理,因为它们随时都可能变为零。几乎每一次弱指针到强指针的转换都必须进行 nil 检查。通常,弱指针会在意想不到的时候变为零。
尽管如此,弱指针仍然存在于许多语言中,因为它们非常有用。弱指针的主要使用场景与高效内存管理和回收有关。
可能的场景包含但不限于:
- 为规范化映射有效管理内存,或为生命周期与另一个对象的生命周期绑定的内存(类似于 JavaScript 的 WeakMap)有效管理内存。
- 弱指针的另一个良好用例是向 GC 提示:可以放弃某些资源,因为以后重建这些资源的成本很低,尤其是在这些资源占用大量内存的情况下。
弱指针 API 设计
Go 官方计划新增一个 weak 包,添加以下 API 作为弱指针的使用:
type Pointer[T any] struct { ... }
func Make[T any](ptr *T "T any") Pointer[T] { ... }
func (p Pointer[T]) Value() *T { ... }
这些 API 主要用于创建和管理弱指针。Pointer[T] 是一个弱指针类型,能够引用类型为 T 的值,但不会阻止该值被垃圾回收。
使用 Make 函数可以从一个有效的指针中创建一个弱指针,而 Value 方法则返回原始指针,若值已被回收则返回 nil。
弱指针的比较遵循特定规则,例如:同一对象的不同字段创建的弱指针不相等,且如果对象通过 runtime.SetFinalizer 复活,之前的弱指针也会失效。
这样的设计目的是为了有效管理内存并避免内存泄漏。
使用例子
前 Go 核心团队负责人 rsc 在接纳这个提案时,设计了一个弱缓存抽象的例子。
代码如下:
type Cache[K any, V any] struct {
f func(*K) V
m atomic.Map[weak.Pointer[K], func() V]
}
func NewCache[K comparable, V any](f func(*K "K comparable, V any")V) *Cache[K, V] {
return &Cache[K, V]{f: f}
}
func (c *Cache[K, V]) Get(k *K) V {
kw := weak.Make(k)
vf, ok := c.m.Load(kw)
if ok {
return vf()
}
vf = sync.OnceValue(func() V { return c.f(k) })
vf, loaded := c.m.LoadOrStore(kw)
if !loaded {
// Stored kw→vf to c.m; add the cleanup.
runtime.AddCleanup(k, c.cleanup, kw)
}
return vf()
}
func (c *Cache[K, V]) cleanup(kw weak.Pointer[K]) {
c.m.Delete(kw)
}
var cached = NewCache(expensiveComputation)
部分社区争议
争议主要集中在以下几个方面:
- 必要性:部分开发者质疑弱指针是否解决了实际问题,是否足够常用。
- 复杂性:有人担心引入弱指针会增加代码复杂性,尤其对新手开发者。
- 性能开销:讨论弱指针可能带来的性能影响,是否值得引入。
- 兼容性:考虑弱指针与现有内存管理机制的兼容性。
- 接口设计:对弱指针的具体实现和接口设计存在不同意见。
总的来讲,围绕着弱指针的实际需求、使用复杂性及其对性能的影响。还有如何设计其接口等问题展开。
注:问题都是问题。rsc 已经以一己之力把这个提案往 Accepted 推了。
总结
后续弱指针的引用,可以给 Go 的使用打开一个新的 “后门”。随着这个趋势的不断形成,个人觉得以后 Go 也能有一派开发者针对自己的高性能要求的代码场景玩出一波骚操作了。