Rust引用计数智能指针

在 Rust 中,Arc 和 Rc 都是用于实现共享所有权引用计数智能指针。它们允许一个数据拥有多个“所有者”,直到最后一个所有者消失,数据才会被释放。
它们最大的区别在于:线程安全性

1. 核心对比

特性Rc<T>Arc<T>
全称 Reference Counted (引用计数) Atomic Reference Counted (原子引用计数)
线程安全 不安全 安全
性能 较快 (普通的整数加减) 较慢 (原子操作有硬件开销)
适用场景 单线程内的共享数据 多线程间的共享数据

2. 深度解析

Rc<T>单线程的利器
  • 原理:在堆上存储数据一个简单的整数计数器
  • 优点:操作极其廉价,几乎没有性能负担。
  • 缺点:如果尝试将 Rc 传递给另一个线程,Rust 编译器会报错。因为它内部的计数器不是原子的,并发修改会导致计数错误,进而引发内存安全问题。
Arc<T>多线程的基石
  • 原理:使用 原子操作(Atomic Operations) 来更新引用计数
  • 优点:它是线程安全的,可以放心地在不同线程间 clone 并传递。
  • 代价:原子操作需要 CPU 锁定总线或缓存行,比普通加减法要慢。虽然单次开销很小,但在极高性能的循环中会有感知。

3. 共同的局限性

无论 Arc 还是 Rc,它们都遵循以下规则:
  • 只读访问:它们默认只提供内部数据的不可变引用。如果你需要修改数据,必须配合“内部可变性”锁,例如 Arc<Mutex<T>> 或 Rc<RefCell<T>>
  • 循环引用:如果两个指针互相指向对方,计数器永远不会归零,导致内存泄漏。解决办法是使用 Weak(弱引用)。

4. 开发建议:如何选择?

高性能底层开发中,选择原则非常清晰:
  • 默认选 Rc:如果你能确定数据只会在一个解析线程内部流动,用 Rc 性能最优
  • 必须选 Arc:如果你的数据是全局配置、规则集,或者是需要被多个工作线程(Worker Threads)共同访问的状态,必须使用 Arc
代码示例
你需要在循环外部对所有的 handles 调用 join()
use std::sync::Arc;
use std::thread;

fn main() {
    let safe_val = Arc::new(100);
    let mut handles = vec![];

    for _ in 0..3 {
        let child_val = Arc::clone(&safe_val);
        let handle = thread::spawn(move || {
            // 只要 safe_val 实现了 Display 或 Debug,这里就能输出
            println!("线程内读取: {}", child_val);
        });
        handles.push(handle);
    }

    // 关键:等待所有线程执行完毕
    for handle in handles {
        handle.join().unwrap();
    }
}
如果处理多线程问题,可以继续讨论如何配合 Mutex 或 RwLock 来实现安全修改。

参考资料:

1.引用循环与内存泄漏

2.rust引用计数智能指针Rc<T>

posted @ 2026-02-05 15:01  PKICA  阅读(6)  评论(0)    收藏  举报