1 线程
调用 thread::spawn 函数创建线程并传递一个闭包,包含新线程运行的代码:
use std::thread;
use std::time::Duration;
fn main() {
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));
}
}
使用 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
闭包经常与 thread::spawn
一起使用,允许在一个线程中使用另一个线程的数据。使用 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();
}
2 消息传递
Rust 中一个实现消息传递并发的主要工具是通道(channel),由发送者(transmitter)和接收者(receiver)组成。可以使用 mpsc::channel
函数创建一个新的通道,mpsc
是 多个生产者,单个消费者(multiple producer, single consumer)的缩写。mpsc::channel
函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。
use std::thread;
use std::sync::mpsc;
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);
}
3 共享内存
在某种程度上,任何编程语言中的通道都类似于单所有权,因为一旦将一个值传送到通道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。
互斥器(mutex)是 mutual exclusion 的缩写,任意时刻只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的锁(lock)来表明其希望访问数据,使用时需要注意如下:
- 在使用数据之前尝试获取锁;
- 处理完被互斥器所保护的数据之后,必须解锁数据;
单线程使用如下:
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
Mutex<T>
是一个智能指针,lock()
返回 MutexGuard
的智能指针,这个智能指针实现了 Deref
指向其内部数据,也提供了一个 Drop
实现当 MutexGuard
离开作用域时自动释放锁。
多线程使用如下:
use std::sync::{Mutex, Arc};
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());
}
这里的 counter
使用了 Arc<T>
进行包装,原因如下:
-
counter
需要让多个线程进获得所有权,需要使用智能指针; -
Rc<T>
不能安全的在线程间共享,当Rc<T>
管理引用计数时,必须在每一个clone
调用时增加计数,并在每一个克隆被丢弃时减少计数,但是Rc<T>
并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值;Arc<T>
类似Rc<T>
并且可以安全的用于并发环境的类型,字母 “a” 代表原子性(atomic),所以这是一个原子引用计数(atomically reference counted)类型。其原子性会带有性能的消耗。
4 Send trait 和 Sync trait
Send
trait 表明类型的所有权可以在线程间传递。几乎所有的 Rust 类型都是Send 的,不过有一些例外,包括 Rc<T>
,因为如果克隆了 Rc<T>
的值并尝试将克隆的所有权转移到另一个线程,这两个线程都可能同时更新引用计数,为此,Rc<T>
被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价,而使用标记为 Send
的 Arc<T>
时,就没有问题了。任何完全由 Send
的类型组成的类型也会自动被标记为 Send
,几乎所有基本类型都是 Send 的。
Sync
trait 表明一个实现了 Sync
的类型可以安全的在多个线程中拥有其值的引用。换一种方式来说,对于任意类型 T
,如果 &T
是 Send
的话 T
就是 Sync
的,这意味着其引用就可以安全的发送到另一个线程。类似于 Send
的情况,基本类型是 Sync
的,完全由 Sync
的类型组成的类型也是 Sync
的。
通常并不需要手动实现 Send
和 Sync
trait,因为由 Send
和 Sync
的类型组成的类型,自动就是 Send
和 Sync
的。他们只是用来加强并发相关的不可变性的。手动实现这些标记 trait 涉及到编写不安全的 Rust 代码。
参考
https://kaisery.github.io/trpl-zh-cn/ch16-00-concurrency.html
网友评论