Rust_2025:阶段1:day6.2 Box ,Cow ,Rc ,Refcell ,Arc,线程(join(),lock(),子线程与主线程通信
并发编程
- 程序可以有多个线程栈,但只能有一个堆
- 子线程崩溃不会导致影响主线程,但主线程崩溃会导致程序停止,自然子线程也就死了
堆类型和堆指针
Box
- Box可以创建一个新的堆,可以在堆上自由修改数据
- 基本用法
let p = Box::new(Point { x: 1, y: 2 }); //自动推断类型并赋给p
let b = Box::new(5); //自动识别为i32,付给b一个32位整形值
let b = Box::new<u32>(5); //强制赋值u32
注:与cpp不同,new的形参是初始化,并非内存大小
- 特点
- Box类型的指针拥有Box类型的所有权
- 只有自己能修改这块堆的值,无法给予其他人可变引用
Box实现链表
#[derive(PartialEq, Debug)]
pub enum List {Cons(i32, Box<List>),Nil,
}fn main() {println!("This is an empty cons list: {:?}", create_empty_list());println!("This is a non-empty cons list: {:?}",create_non_empty_list());
}pub fn create_empty_list() -> List {List::Nil
}pub fn create_non_empty_list() -> List {List::Cons(0 , Box::new(List::Nil))
}
Cow
- 这种指针可以指向任意的堆区域,获取其不可变引用。
- to_mut()方法可以复制指向的堆区域,原来的指针名获取其所有权。
fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {for i in 0..input.len() {let v = input[i];if v < 0 {// Clones into a vector if not already owned.input.to_mut()[i] = -v;}}input
}
Rc
- 特点:可以使一个堆区域获得多个不可变引用。
Refcell
- 特点: 可以使一个堆区域获得一个可变引用权,但只能使用方法隐式调用(运行时可以检测是否有多个可变引用)。
use std::cell::RefCell;
let data = RefCell::new(5);
*data.borrow_mut() = 10; // 修改数据
println!("{}", data.borrow()); // 读取数据
Refcell+Rc组合使用获取多个可变引用
use std::rc::Rc;
use std::cell::RefCell;struct Node {value: i32,next: Option<Rc<RefCell<Node>>>,
}fn main() {let a = Rc::new(RefCell::new(Node { value: 1, next: None }));let b = Rc::new(RefCell::new(Node { value: 2, next: Some(a.clone()) }));// 多个 Rc 指针可以共享同一个 Node// RefCell 允许在 Rc 管理下对 Node 进行可变借用b.borrow_mut().value = 3;
}
为什么必须要Rc套Refcell而并非Refcell套Rc?
- 我的答案是:Refcell并非本身能使堆区域可变,而是可以用方法操控其堆区域。
- 如果是Refcell套Rc的new,则Refcell只能通过方法修改Rc的索引的堆区域,而并非数据堆区域。
Arc
fn main() {let numbers: Vec<_> = (0..100u32).collect();let shared_numbers = Arc::new(numbers);// TODOlet mut joinhandles = Vec::new();for offset in 0..8 {let child_numbers = Arc::clone(&shared_numbers);// TODOjoinhandles.push(thread::spawn(move || {let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();println!("Sum of offset {} is {}", offset, sum);}));}for handle in joinhandles.into_iter() {handle.join().unwrap();}
}
- Arc特指用在多个线程中的Rc
- spawn会创建一个线程。spawn包含一个闭包参数(这里的move是可以自动识别闭包中需要的变量),创造一个子线程,此时主线程会继续运行。
- joinhandles会存储线程信息。对joinhandles向量的一个元素调用join()会获取其线程信息,并检查是否异常。
- 若线程正常结束,join()会返回Ok(返回值),否则会返回Err()。
Mutrex
- 类比Refcell之于Rc就是Mutrex之于Arc。
rust编译器自动解引用
- 我认为,赋值的时候rust会自动解引用(因此Rust从不会返回引用地址)
- 而在其他时候都不会自动解引用(传参)
Arc配合Mutrex
fn main() {let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));let mut handles = vec![];for _ in 0..10 {let status_shared = Arc::clone(&status);let handle = thread::spawn(move || {thread::sleep(Duration::from_millis(250));// TODO: You must take an action before you update a shared valuelet mut status = status_shared.lock().unwrap();status.jobs_completed += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();// TODO: Print the value of the JobStatus.jobs_completed. Did you notice// anything interesting in the output? Do you have to 'join' on all the// handles?}println!("jobs completed {}", status.lock().unwrap().jobs_completed);
}
- sleep只是普通的等待函数。
- Mutrex通过.lock()函数给线程中的变量一把锁(新的状态),当变量的生命周期结束后,锁才能给其他线程。拥有锁的变量才能读写Mutrex变量。
- lock()有三种状态:阻塞,成功,失败。
子线程与主线程通信
fn main() {let (tx, rx) = mpsc::channel();let queue = Queue::new();let queue_length = queue.length;send_tx(queue, tx);let mut total_received: u32 = 0;for received in rx {println!("Got: {}", received);total_received += 1;}println!("total numbers received: {}", total_received);assert_eq!(total_received, queue_length)
}
- mpsc是定义一组发送与接收端的函数
- tx是发送,rx是接收
- 当所有的发送端消失,对于rx的循环才会结束。