Go 团队在实现 unique 包的时候,同时孵化出两个提案:
先来了解 weak 提案,大概率会增加一个 weak 包,至于是不是放在 unsafe 等其它包也不是不行.
弱指针(或在其他语言中称为 弱引用)允许开发者引用内存,而不会阻止垃圾回收器回收这部分内存. 为了避免出现明显的悬空引用,当引用的内存被回收时,弱指针会变为 nil. 弱指针可以转换为常规的(“强”)指针,强指针会阻止垃圾回收器回收内存,并允许正常使用和访问这部分内存.
与常规指针相比,弱指针通常稍微难以处理,因为它们随时可能变为 nil. 几乎每次从弱指针转换为强指针时,都必须进行 nil 检查. 而且,弱指针往往会在意想不到的时候变为 nil.
尽管如此,许多语言中仍然存在弱指针,因为它们非常有用. 弱指针的主要使用场景与高效的内存管理和回收有关. 例如,它们可以有效管理用于规范化映射的内存,或管理生命周期依赖于另一个对象的内存(类似于 JavaScript 的 WeakMap). 另一个弱指针的常见用例是向垃圾回收器提示可以丢弃某些资源,因为这些资源可以很便宜地重新构建,特别是当这些资源占用大量内存时.
首先我们先看看其他编程语言的弱指针或者弱引用.
std::rc::Weak 是与 Rc(Reference Counted)配对使用的弱引用,适用于单线程场景. Rc 通过引用计数管理对象的所有权,每个 Rc 引用会增加计数,只有当计数为 0 时对象才会被回收. 然而,在某些场景中会出现循环引用问题,使得计数永远不会归零,这时 Weak 就可以避免这个问题.
Weak 是对 Rc 的弱引用,创建 Weak 不会增加引用计数,因此不会影响对象的生命周期. Weak 可以通过 upgrade() 方法尝试将其转换为 Rc,如果对象仍然存在,则返回 Some(Rc),否则返回 None.
std::sync::Weak 是与 Arc(Atomic Reference Counted)配对使用的弱引用,适用于多线程环境. 与 Rc 类似,Arc 也使用引用计数来管理对象的所有权,但它是线程安全的,通过原子操作来保证在多线程下的安全性. Weak 在多线程环境下避免了循环引用,同时不影响对象的生命周期.
std::sync::Weak 的工作方式与 std::rc::Weak 类似. 使用 Arc::downgrade 可以创建一个弱引用,使用 Weak::upgrade 可以尝试获得一个有效的强引用(Arc). 如果对象已经被回收,upgrade() 返回 None.
总结:
-
std::rc::Weak和std::sync::Weak确实是 Rust 中的弱指针实现,分别用于单线程和多线程环境. -
Weak指针不会增加引用计数,避免了循环引用问题. -
通过
upgrade()方法可以将Weak转换为Option<Rc<T>>,确保安全访问.
代码检查:
use std::rc::{Rc, Weak};
struct Node {
value: i32,
next: Option<Rc<Node>>,
prev: Option<Weak<Node>>, // 弱引用,防止循环引用
}
fn main() {
let node1 = Rc::new(Node { value: 1, next: None, prev: None });
let node2 = Rc::new(Node { value: 2, next: Some(Rc::clone(&node1)), prev: Some(Rc::downgrade(&node1)) });
// 使用 weak 引用的升级来访问节点
if let Some(prev_node) = node2.prev.as_ref().unwrap().upgrade() {
println!("Previous node value: {}", prev_node.value);
} else {
println!("Previous node has been dropped");
}
if let Some(next_node) = node1.next.as_ref() {
println!("Next node value: {}", next_node.value);
}else {
println!("Next node has been dropped");
}
}在 Java 中,WeakReference 是用于管理对象引用的一个特殊类,它与 Java 的垃圾回收机制(Garbage Collection, GC)密切配合,允许对象在没有强引用时被回收. WeakReference 的主要作用是避免对象因非必要引用而无法被 GC 回收,尤其适用于缓存和监听器等场景.
WeakReference 是 Java 中的弱引用类,位于 java.lang.ref 包中. 它的主要特点是,若一个对象仅通过弱引用指向它,则当垃圾回收器运行时,该对象可以被回收,而不会因为弱引用的存在而延迟回收. 这样可以有效防止内存泄漏问题,尤其是在需要缓存大量对象的场景中.
在 Java 中,对象的引用分为四种类型:强引用、软引用、弱引用 和 虚引用. 其中,WeakReference 代表弱引用. 垃圾回收器在进行内存回收时,会忽略掉弱引用的对象,也就是说,如果某个对象只有弱引用指向它,GC 会在下一次垃圾回收时直接回收该对象.
总结:
-
WeakReference是 Java 中用于避免对象因非必要引用无法被 GC 回收的机制. -
通过
get()方法访问弱引用对象,GC 后对象可能为null.
代码检查:
import java.lang.ref.WeakReference;
public class WeakRefExample {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 强引用置为 null
obj = null;
// 尝试通过弱引用访问对象
if (weakRef.get() != null) {
System.out.println("对象仍然存在");
} else {
System.out.println("对象已被垃圾回收");
}
}
}通过调用 get() 方法,可以尝试从弱引用中获取对象. 如果对象尚未被垃圾回收,get() 方法会返回一个强引用;否则,返回 null. 一旦你通过 get() 获得了对象的强引用,那么这个对象在该强引用的生命周期内不会被回收.
在 C# 中,WeakReference 是一个用于引用对象的特殊类,允许开发者引用对象而不会阻止该对象被垃圾回收器(GC)回收. 它在防止内存泄漏、缓存等场景中非常有用,尤其是当需要引用对象但不希望这个引用阻止垃圾回收时.
WeakReference 是 .NET 提供的弱引用类,用于引用可能会被垃圾回收的对象. 与强引用不同,WeakReference 不会阻止对象被垃圾回收. 如果某个对象仅通过弱引用指向它,并且没有其他强引用,那么该对象仍然可以在垃圾回收时被回收.
你可以通过 WeakReference.Target 属性访问弱引用的对象,但当该对象被垃圾回收时,Target 会返回 null.
通过获取 Target 属性(或者使用 WeakReference<T>.TryGetTarget 方法),你可以尝试将弱引用转换为强引用.
总结:
-
WeakReference和WeakReference<T>是 C# 中用于防止对象被强引用阻止 GC 的机制. -
通过
Target属性访问对象,使用IsAlive检查对象是否存在.
代码检查:
using System;
class Program {
static void Main() {
var obj = new object();
WeakReference weakRef = new WeakReference(obj);
// 强引用置为 null
obj = null;
// 尝试通过弱引用访问对象
if (weakRef.IsAlive) {
Console.WriteLine("对象仍然存在");
} else {
Console.WriteLine("对象已被垃圾回收");
}
}
}Michael Knyszek 增加了一个提案,在 Go 语言中已经被接受 (目前看提案的大 Boss 还是 Russ Cox, 这个重要的职位貌似没有交出去),并且很可能在 Go 1.24 中实现.
这个提案提议增加如下的弱指针的创建和使用:
// Pointer 是一个指向类型 T 的弱指针.
//
// 如果两个 Pointer 值比较相等,那么它们创建时使用的指针也比较相等.
// 即使引用的对象被回收,这一属性仍然保留.
//
// 如果多个弱指针指向同一对象内的不同偏移(例如,指向同一结构体的不同字段),
// 那些指针将不会比较相等.
// 如果从一个变为不可达的对象创建了弱指针,但由于最终处理函数的原因而复活,
// 那么该弱指针将不会与复活后创建的弱指针比较相等.
//
// 调用 Make 方法并传入 nil 指针将返回一个弱指针,
// 其 Value 方法始终返回 nil. Pointer 的零值表现得好像是
// 通过将 nil 传递给 Make 创建的,并且与这样的指针比较相等.
type Pointer[T any] struct { ... }
// Make 从指向类型 T 的值的指针创建一个弱指针.
func Make[T any](ptr *T "T any") Pointer[T] { ... }
// Value 返回用于创建弱指针的原始指针.
// 如果原始指针指向的值被垃圾回收器回收,则返回 nil.
// 如果弱指针指向的对象有最终处理函数,则一旦该对象的最终处理函数被排入执行队列,
// Value 将立即返回 nil.
func (p Pointer[T]) Value() *T { ... }预期的弱指针将在内部通过间接对象表示. 这意味着每个与任何弱指针关联的指针都有一个关联的 8 字节分配,该分配包含一个指针. 这个指针就是实际的弱引用. 这种表示方式在 CPU 上非常高效,因为垃圾回收器可以原子地将单个指针设置为 nil,从而使所有对某个对象的弱引用变为 nil.
在实践中,这种 8 字节的分配通常不会造成问题,因为弱指针通常用于内存效率. 例如,规范化映射已经在去重内存,因此每个规范化副本额外的 8 字节相对便宜. 不过,可能值得在文档中强调这一细节,正如 C# 语言所做的那样.
这种表示方式还有其他好处. 其中一个好处是 API 的相等语义非常自然. 即使在原始指针不再存在后,从同一指针创建的弱指针仍然相等. 这使得可以构建以弱指针为键的映射. 另一个好处是其简单性:为了支持弱指针,垃圾回收器几乎不需要做任何更改.
当然弱引用的使用还是有潜在的风险,比如在和 SetFinalizer 同时使用的场景,弱引用的对象复活时. 虽然这个提案有些争议,但是争议还比较小,还是让我们看看最后的实现效果吗?最终的实现能不能提供一个简单不易出错的 API,让我们拭目以待.
非必要不要使用 weak 包,因为它归属于非常小众的场景,而且很容易出错. 这一点提案的讨论 issue 在各位大咖都不否认这一点. 那么什么场景适合使用 weak 呢? 可以考虑下面几个场景:
-
缓存机制:当不需要强引用缓存数据时,使用弱引用可确保系统在内存不足时回收这些数据.
-
事件处理器和回调:避免由于强引用导致的内存泄漏.
-
大型对象图:在复杂的对象引用结构中,通过弱引用防止循环引用问题.
下面是一个弱指针实现缓存的例子:
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)