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: 获取值.
posted @ 2024-10-11 15:07  杂役24  阅读(24)  评论(0)    收藏  举报