Rust Lang Book Ch.16 Concurrency
使用线程来并行化任务一方面可以加快速度,避免因为IO等待耗费太久时间,另外一方面,也带来了资源竞争,死锁和潜在的难以复现的种种bug。不同的编程语言采取不同策略来生成管理调度线程,如果用户在编程语言中申请一个线程,就通过系统接口获取系统的一个线程,那么就称之为1:1模型。编程语言提供的线程被称为green thread,每M个green threads对应N个系统线程的模型就被称之为M:N模型。为了保证Rust在每个binary上附带的runtime code,即额外需要的用于运行的代码最少,Rust只提供了1:1线程模型。
thread::spawn
Rust使用thread::spawn来创建简单的线程JoinHandle,并且可以用JoinHandle::join()来等待所有的线程返回
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }
move使得线程能访问另一个线程中的数据
线程可以使用move来获得另一个线程中拥有的变量(captured variable)的ownership,当然,在move之后,这个变量就不能在原来的线程中使用了。
use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); }); handle.join().unwrap(); }
如果尝试继续用,比如试着drop,就会报错:
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
drop(v); // oh no!
^ value used here after move
handle.join().unwrap();
}
channel
channel可以认为有两半:transmitter发送端和receiver接收端。无论是接受还是发送端drop掉了,都可以认为是channel已经关闭了。
mpsc::channel中的mpsc的包意指multiple producer single consumer,即多个发送者,一个接收者。
发送端可以使用send方法发送数据,接收端可以使用recv或者try_recv来接收数据。对于send方法来说,接收端已经drop掉了会导致返回错误。recv会阻塞直到接收到数据,同样的,如果发送端已经关闭了就会报错。try_recv则不会阻塞。
use std::sync::mpsc; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {}", received); }
此外,send会导致ownership的转移,所以send的参数是不能继续用了。
use std::sync::mpsc; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); --- move occurs because `val` has type `std::string::String`, which does not implement the `Copy` trait tx.send(val).unwrap(); --- value moved here println!("val is {}", val); ^^^ value borrowed here after move }); let received = rx.recv().unwrap(); println!("Got: {}", received); }
Shared-State Concurrency
Mutex<T>+ Arc<T>
Mutex本身是个Smart Pointer,Mutex::lock返回一个LockResult结构,里面的Smart Pointer结构MutexGuard能够获取指向数据本身的引用,而Drop会自动释放锁。注意在传给不同线程的时候,可以结合Arc(Atomic Reference Counting)一起使用以传递Mutex本身,因为多个线程必须共享同个Mutex对象。此外,Rc不是线程安全的在这里不能用。Mutex与Arc的组合,正如RefCell和Rc的组合,前者提供了interior mutability,后者则分发引用。
use std::sync::Mutex;
use std::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());
}
Extensible Concurrency with the Sync and Send traits
Rust语言自身的并发功能很少,大部分并发功能都是在standard library而不是core中实现的。程序员可以通过std::marker中的两个traits Sync和Send来自定义并发功能。Sync和Send都是unsafe的。
Send特性说明实现了该特性的类型是可以在线程之间交换ownership的。Sync特性则说明它的引用可以安全地发送给其他线程,即可以被多个线程所引用。