Rust学习笔记(五十)共享状态的并发及用Sync和Send扩展并发
第四十九篇我们学习了在多线程中使用通信来实现并发,这一篇学习使用共享内存的方式实现并发。 Rust支持通过共享状态来实现并发。Channel类似单所有权:一旦值的所有权转移至Channel,就无法使用它了。而共享内存并发类似于多所有权:多个线程可以同时访问同一块内存。 使用Mutex来每次只允许一个线程来访问数据
Mutex是mutual exclusion(互斥锁)的缩写。在同一时刻,Mutex只允许一个线程来访问某些数据。 想要访问数据: 线程必须首先获得互斥锁(lock),lock数据结构是mutex的一部分,它能跟踪谁对数据拥有独占访问权 mutex通常被描述为:通过锁定系统来保护它所持有的数据 Mutex的两条规则在使用数据之前,必须尝试获取锁(lock) 使用完mutex所保护的数据,必须对数据进行解锁,以便于其它线程可以获取锁 Mutex的API
通过Mutex::new(数据)来创建Mutex,Mutex是一个智能指针。访问数据前,通过lock方法来获得锁: 这个方法会阻塞当前线程 lock可能会失败 返回的是MutexGuard(智能指针,实现了Deref和Drop)
例: use std::sync::{Mutex, MutexGuard}; fn main() { let m = Mutex::new(5); { let mut num: MutexGuard = m.lock().unwrap(); *num = 6;//由于MutexGuard实现了Deref,所以可以使用*解引用 }//由于MutexGuard实现了Drop,所以离开作用域会被释放,这样就不用手动释放锁了。 println!("m = {:?}", m) } 多线程共享Mutex
例: use std::{sync::{Mutex}, thread}; fn main() { let counter = Mutex::new(0); let mut handles = vec![]; for _ in 0..10{ let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle) } for handle in handles { handle.join(); } println!("Result = {}", *counter.lock().unwrap()) }
运行以上代码会报错,因为在第一个循环中,第一次进入循环时创建了线程,并把counter的所有权移动至闭包内了,那么剩下的循环就无法获得所有权了。 多线程的多重所有权,使用Arc来进行原子引用计数
之前学过Rc可以使数据被多重引用,但是它只能在单线程下使用。 Arc和Rc类似,它可以用于并发场景。A:atomic原子的。 那么为什么所有的基础类型都不是原子的,为什么标准库类型不默认使用Arc。因为需要牺牲性能作为代价。Arc和Rc的API是相同的。
例: use std::{ sync::{Arc, Mutex}, thread, }; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle) } for handle in handles { handle.join().unwrap(); } println!("Result = {}", *counter.lock().unwrap()) }
运行代码输出10。 RefCell/Rc vs Mutex/Arc
Mutex提供内部可变性,和Cell家族(比如RefCell)一样。 我们使用RefCell来改变Rc里面的内容;同样使用Mutex来改变Arc里面的内容。 注意:Mutex有死锁风险。 Send和Sync trait
Rust语言的并发特性比较少,目前学的并发特性都来自标准库(而不是语言本身)。所以我们无需局限于标准库的并发,可以自己实现并发。 但在Rust语言中有两个并发的概念:td::marker::Sync和std::marker::Send这两个trait。 Send允许线程间转移所有权
实现Send trait的类型可以在线程间转移所有权。Rust中几乎所有类型都实现了Send,但是Rc没有实现,所以它只能用于单线程。 任何完全由实现了Send的类型组成的类型也被标记为Send。除了原始指针之外,几乎所有基础类型都实现了Send。 Sync允许从多线程访问
实现了Sync的类型可以安全的被多个线程引用。也就是说,如果T是Sync,那么&T就是Send,即引用可以被安全的送往另一个线程。 基础类型都是Sync,完全由Sync类型组成的类型也是Sync,但Rc不是Sync的;RefCell和Cell家族也不是Sync的。不过Mutex是Sync的。
通常并不需要手动实现 Send 和 Sync trait,因为由 Send 和 Sync 的类型组成的类型,自动就是 Send 和 Sync 的。因为他们是标记 trait,甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。
手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,需要十分以及特别的小心。