[重学Rust]之智能指针
✨重磅!盹猫的个人小站正式上线啦~诚邀各位技术大佬前来探秘!✨
这里有:
- 硬核技术干货:编程技巧、开发经验、踩坑指南,带你解锁技术新姿势!
- 趣味开发日常:代码背后的脑洞故事、工具测评,让技术圈不再枯燥~
- 独家资源分享:开源项目、学习资料包,助你打怪升级快人一步!
👉 点击直达→ 盹猫猫的个人小站 👈
🌟 来逛逛吧,说不定能挖到你正在找的技术宝藏哦~
目录
⭐ 前言
环境准备
代码步骤
Box
RefCell
Rc/Arc
Mutex
RwLock
疑问解答
🤔 有了Mutex为什么还要用RwLock?
总结
欢迎来到 盹猫(>^ω^<)的博客
本篇文章主要介绍了
[[重学Rust]之智能指针]
❤博主广交技术好友,喜欢文章的可以关注一下❤
⭐ 前言
编写这个系列文章的目的是为了巩固所学的Rust知识,对Rust中庞大的依赖体系进行整理,当然,如果文章内容可以帮助到你,那当然求之不得了😁
记录Rust中的智能指针
🌹学而时习之,不亦说乎!🌹
环境准备
除了基础的Cargo环境之外,你还需要准备以下环境,如果你没有安装Cargo环境可以通过盹猫脚本库进行Rust环境的快速搭建工作:
[package]
name = "ref_test"
version = "0.1.0"
edition = "2024"[dependencies]
因为智能指针主要用到标准库中的依赖,所以不用添加任何其它依赖项.
代码步骤
Box
Box允许在堆上开辟一小块内存,用于存储定义的值,与直接定义不同的是,它的值存储在堆上,同时保留一个指针指向堆上的数据,如下示例:
let box1 = Box::new(5);println!("栈指针:{:p}", &box1);println!("堆指针:{:p}", &*box1);
上述代码会打印出存储于栈上的指针地址和存储上堆上的数据地址:
栈指针:0x7ffc262e85a8
堆指针:0x55e588148d00
🚩 Box可以在堆上创建一块固定大小的内存地址用于存储数据.
RefCell
RefCell可以在运行态动态获取数据不可变引用的可变引用,即将不可变数据动态的变为可变数据,这个数据在编译态是不可变的,如下示例:
let mut p = 5;
{p += 5;
}
println!("{}", p);
该定义为编译态已定义好的为可变引用,如果是不可变引用,不可以在后续的代码中进行动态更改.
RefCell就解决了这个问题,如下示例:
let rfc = RefCell::new(5);{let mut p = rfc.borrow_mut();*p += 5;}println!("最终值:{:?}", rfc.into_inner());
在该示例中定义5为Refcell类型的不可变引用,但是可以在代码运行过程中调用borrow_mut对引用类型进行更改为可变引用,当离开作用域时,放弃可变借用权限,得出如下打印:
最终值:10
🚩 RefCell可以保证在同一时间只有一个可变借用,保证了线程安全.
Rc/Arc
Rc可以保证有多个所有者共享数据,而Arc可以保证在多个线程内有多个所有者共享数据,它使用引用计数来统计所有者数量,如下示例:
let c = Rc::new(5);let p = Rc::clone(&c);println!("{}", p);
println!("c的指针地址:{:p}", &c);
println!("c的数据地址:{:p}", &*c);
println!("p的指针地址:{:p}", &p);
println!("p的数据地址:{:p}", &*p);
上面的代码打印如下数据信息:
c的指针地址:0x7ffc21c5f780
c的数据地址:0x5566c676cd10
p的指针地址:0x7ffc21c5f788
p的数据地址:0x5566c676cd10
可以看到c和p的数据地址是相同的,也就是两个都是指向同一个数据地址.
Arc在多线程中共享数据引用计数的示例:
let arc_data = Arc::new(5);let mut handles = vec![];for _i in 0..3 {let c = Arc::clone(&arc_data);//创建多个子线程let d = thread::spawn(move || {println!("c的值:{}", c);println!("引用数量:{}", Arc::strong_count(&c));});handles.push(d);}//等待执行完成for handle in handles {handle.join().unwrap();}println!("引用数量:{}", Arc::strong_count(&arc_data));
打印如下内容:
c的值:5
c的值:5
引用数量:4
c的值:5
引用数量:3
引用数量:4
引用数量:1
可以看到在最后引用计数变为1,也就是初始的引用数量.
🚩 Rc/Arc 可以保证在不改变数据地址的情况下,只通过增加引用计数的方式获取数据的共享权力.
Mutex
Mutex可以在多线程进行数据的访问,因为它可以创建一个互斥锁,保证同一时间只有一个线程可以访问数据信息,通常和Arc配合使用完成多线程的数据更新操作,示例如下:
let p = Arc::new(Mutex::new(5));let mut handles = vec![];for i in 0..3 {// 多线程数据共享let updater = Arc::clone(&p);let c = thread::spawn(move || {//在更新前锁定数据let mut p = updater.lock().unwrap();*p += i;});handles.push(c);}for i in handles {i.join().unwrap();}println!("最终结果:{}", p.lock().unwrap())
上述代码中通过Arc实现多线程的数据共享操作,同时内部为一个Mutex来控制在更新前进行锁定操作,保证同一时间只有一个线程访问,则最后的输出结构应该是8:
最终结果:8
🚩 Mutex可以保证在多线程访问时只有一个线程可以操作数据,其它线程只能等待.
RwLock
RwLock与Mutex用处几乎相同,但Mutex是进行原子性的操作,RwLock更多的是用来进行读多写少的场景,因为同一时间可以有多个读,但只能有一个写,示例如下:
use std::{sync::{Arc, RwLock},thread,
};fn main() {let p = Arc::new(RwLock::new(5));let mut handles = vec![];for i in 0..3 {let updater = Arc::clone(&p);let c = thread::spawn(move || {let mut d = updater.write().unwrap();*d += i;//释放写锁,否则会竞争,造成死锁drop(d);println!("{}", updater.read().unwrap());});handles.push(c);}for i in handles {i.join().unwrap();}println!("最终值:{}", p.read().unwrap())
}
这里使用updater.write()获取了写锁,但是下面又进行了读锁的获取,所以中间使用drop(d)释放写锁,否则运行会造成死锁.
疑问解答
🤔 有了Mutex为什么还要用RwLock?
Mutex
(互斥锁):采用 “全互斥” 策略 ——无论读操作还是写操作,同一时间只允许一个线程访问数据。
RwLock
(读写锁):采用 “读写分离” 策略 ——区分 “读操作” 和 “写操作”,允许多个线程同时读,但写操作与任何操作(读 / 写)互斥。
总结
以上就是对Rust中智能指针的记录了,通过区分不同的智能指针.知道了Rust针对不同的共享性,可访问性进行了区分,通过组合使用不同的智能指针可以解决单线程和多线程中应用的数据共享和访问场景.
如果内容对你有帮助,麻烦留一个赞👍和收藏⭐支持一下!
如果你对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!