愿世界充满和平, 愿每个人都能享有自由与尊重.|

杂役24

园龄:1年11个月粉丝:0关注:0

2024-10-11 16:20阅读: 36评论: 0推荐: 0

Rust 智能指针

指针: 就是一个变量, 只不过这个变量里面保存的是其它变量的内存地址.

变量 s 就是一个指针, 里面保存了 String 值的内存地址.

layout-1.png

智能指针

智能指针其实就是一个结构体.

它里面保存了一个指针, 然后再根据不同的智能指针类型, 保存一些相关的数据, 比如数据长度、容量、引用计数等等.

然后又实现了三个 trait:

  • Defef
  • DefefMut
  • Drop
layout-2.png

智能指针和多线程

数据类型和多线程对比.jpg

Box

先看一下, 下面代码的内存分配:

struct test {
num :i32,
s1 :String,
}
fn main() {
let t = test{num: 10, s1: String::from("value")};
// ...
}

test 本身 (结构体实例的内存布局) 是分配在栈内存, num、s1 的指针、len、cap 也是保存在栈内存的.

Rust-结构体实例内存分配.drawio.jpg

但是可以通过 Box 将 test 实例分配到堆内存.

Box 只是将数据分配到堆内存, 然后使用数据的时候 Box 会自动解引用.

所以 Box 是不是线程安全的和它的泛型参数有关系, 如果 Box 的泛型是线程安全的, 那么 Box 就是线程安全的.

// Box::new 会返回一个指向 test 实例的一个指针,
// 然后把这个指针保存在栈内存上.
let test = Box::new(test{num:10, s1:String::from("")});
// 也可以将数字、字符等放到堆内存上.
// Box::new(10);
Rust-智能指针内存分配-box.drawio.jpg

使用:

// 在 Box 实例上使用解引用符号 *, 把里面的堆上的值再次移动回栈上
let boxed: Box<u8> = Box::new(5);
let val: u8 = *boxed; // 这里这个 val 整数实例就是在栈上的值
println!("{:?}", val);
println!("{:?}", boxed); // 对于 u8 类型, 解引用后 boxed 实例还能用
// 如果 let b = Box::new(test{num:10, s1:String::from("")}); 包装的是一个结构体,
// 那么可以直接通过变量 b 调用结构体中的方法或属性等.

Rc、Arc

  • Rc: 非线程安全
  • Arc: 线程安全

Arc、Rc 都是用来共享数据的, 但是不能修改这个数据的值.

即使包装的数据是一个可变引用 Arc::new(&mut struct) 也不能修改这个数据的值.

每次在使用这个数据的时候, 使用计数都会 +1, 使用完成后会 -1, 只有等到资源的引用为 0 的时候, 才会释放这个资源.

use std::rc::Rc;
fn main() {
// 也是保存在堆内存.
let five = Rc::new(5);
// 通过这两种方式都可以增加引用计数,
// 这两种方式没有区别, 喜欢哪种用哪种.
let _five1 = Rc::clone(&five);
let _five2 = five.clone();
// 返回引用计数的数量.
println!("Count after cloning: {}", Rc::strong_count(&five));
}

Arc 的使用:

use std::sync::Arc;
use std::thread;
#[derive(Debug)]
struct Test {
a: i32,
b: i32,
}
fn main() {
let test = Test { a: 1, b: 2 };
// 这里使用 mut 关键字, 没有意义, 因为只能只读.
// let mut test_arc = Arc::new(test);
// let test_arc_clone1 = Arc::clone(&mut test_arc);
let test_arc = Arc::new(test);
let test_arc_clone: Arc<Test> = Arc::clone(&test_arc);
// 必须使用 move 关键字, 将 test_arc_clone 移动到新线程中,
// 防止出现悬垂引用.
let handle = thread::spawn(move || {
// 第一种使用方式, 直接获取值.
// 这种方式就是, 通过 Deref trait「自动解引用」.
// let test: Arc<Test> = test_arc_clone;
// println!("{}, {}", test.a, test_arc_clone.b);
// 第二种使用方式, 需要使用解引用操作符 *「显示解引用」,
// 对 Arc<Test> 解引用后数据类型是 Test.
//
// 但是 Arc 是共享数据, 不能获取所有权, 所以还要使用 & 获取「不可变引用」.
let xx: &Test = &*test_arc_clone;
println!("{}, {}", xx.a, xx.b);
});
handle.join().unwrap();
}

引用循环与内存泄漏

在 Rust 中,使用 Rc<T>RefCell<T> 可以创建循环引用,这可能导致内存泄漏,因为引用计数无法达到零,因此无法自动释放内存。

以下是一个创建循环引用的例子:

use std::rc::Rc;
use std::cell::RefCell;
struct Node {
value: i32,
next: RefCell<Option<Rc<Node>>>,
}
fn main() {
let a = Rc::new(Node {
value: 1,
next: RefCell::new(None),
});
let b = Rc::new(Node {
value: 2,
next: RefCell::new(None),
});
// 制造循环引用
*a.next.borrow_mut() = Some(Rc::clone(&b));
*b.next.borrow_mut() = Some(Rc::clone(&a));
// 运行结果: Value a: 1, b: 2
println!("Value a: {}, b: {}", a.value, b.value);
// 运行结果: Reference count a: 2, b: 2
// 返回值是 2,这是因为 Rc::new 的时候就会对数据进行一次引用,所以返回值是 2。
println!("Reference count a: {}, b: {}", Rc::strong_count(&a), Rc::strong_count(&b));
// 此时a和b相互引用,无法自动释放
}

为了解决这个问题,我们可以使用 Weak<T>Weak<T> 也是一种智能指针,但是它不会增加引用计数,因此它不会阻止数据的释放。

下面是修正后的代码:

use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
value: i32,
next: RefCell<Option<Weak<Node>>>, // 注意这里使用了 Weak.
}
fn main() {
let a = Rc::new(Node {
value: 1,
next: RefCell::new(None),
});
let b = Rc::new(Node {
value: 2,
next: RefCell::new(None),
});
println!("Reference count a: {}, b: {}", Rc::strong_count(&a), Rc::strong_count(&b));
// 使用Weak来避免循环引用
// 通过 downgrade 函数将 Rc 转换为 Weak。
*a.next.borrow_mut() = Some(Rc::downgrade(&b));
*b.next.borrow_mut() = Some(Rc::downgrade(&a));
println!("Value a: {}, b: {}", a.value, b.value);
// Weak指针不会增加Rc的引用计数
println!("Reference count a: {}, b: {}", Rc::strong_count(&a), Rc::strong_count(&b));
} // a和b离开作用域,由于没有其他强引用,引用计数减到0,内存被释放
// 由于使用了Weak指针,即使存在循环引用,也不会导致内存泄漏

在使用值的时候,可以通过 upgrade 方法转换为 Rc<T> 并增加引用计数,保证值不会被释放。

RefCell

RefCell 实现了内部可变性, 它的值是放在堆上还是放在栈上要看值得类型.

在不使用 RefCell 的时候, 要修改一个变量的值, 你需要把这个变量设置为可变的, 但是使用 RefCell 后, 就不需要把这个变量设置为可变的, 而是在对应实例的内部修改.

use std::cell::RefCell;
fn main() {
// Cell 只能存储实现了 Copy 的类型,
// RefCell 可以存储任何类型.
let data = RefCell::new(1);
{
// 获得 RefCell 内部数据的可变借用
let mut v = data.borrow_mut();
*v += 1;
}
// 不可变引用
println!("data: {:?}", data.borrow());
}

Cell 和 RefCell 都是非线程安全的.

外部可变性和内部可变性的重要区别.jpg

AtomicCell

AtomicCell 就是一个线程安全的 RefCell, 通过 crossbeam-utils 提供.

如果 AtomicCell 的泛型类型支持 CAS 那么就会使用 CSA 操作, 否则就会使用锁进行同步.

AtomicCell::<T>::is_lock_free() 返回 true, 表示 T 支持 CAS 操作.

方法和 Atomic 中的方法类似:

  • new: 创建新的 AtomicCell.
  • store: 保存值.
  • swap: 保存值, 并返回旧值.
  • compare_exchange(current, new): 如果值是 current, 就尝试将值修改为 new.
  • load: 获取值.

本文作者:杂役24

本文链接:https://www.cnblogs.com/zy24/p/18458481

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   杂役24  阅读(36)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起