rust多线程
多线程可能导致的问题
- 竞争
- 死锁
- Bug难以复现和修复
实现线程的方式
- OS的API,
1:1
模型 - 语言自己的线程,
M:N
模型
Rust标准库仅提供了1:1
线程的模型。
通过thread::spawn
函数创建新线程
let handle = thread::spawn(|| {
for i in 1..=10 {
println!("spawn thread: {}",i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("main thread: {}",i);
thread::sleep(Duration::from_millis(1));
}
// 交替打印,主线程结束后,整个程序结束。
// 等待子线程执行完毕
handle.join();
move闭包
允许在当前线程使用其他线程的数据。
let v = vec![1,2,3];
let handle = thread::spawn(move || {
// v不能直接用是因为目前线程执行时,
// v不一定还在,例如2处。
println!("{:?}",v); // [1,2,3]
});
// 2
// drop(v);
handle.join().unwrap();
使用消息传递跨线程传递数据
channel
包含发送端和接收端。
mpsc::channel
创建,multi producer, single consumer.
use std::{sync::mpsc, thread};
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap(); // 移交了所有权
//println!("{}",val);
// ^^^ value borrowed here after move
});
});
// recv 阻止线程执行,返回Resule<T,E>
// 当发送端关闭,会收到一个错误
let received = rx.recv().unwrap();
println!("Got: {}", received); // Got hi
}
发送接收多个数据
use std::{sync::mpsc, thread, time::Duration};
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("hello"),
String::from("ha "),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_millis(1));
}
});
for received in rx {
println!("{}",received);
}
}
克隆多个sender
use std::{sync::mpsc, thread, time::Duration};
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx);
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("hello"),
String::from("ha "),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_millis(10000));
}
});
thread::spawn(move || {
let vals = vec![
String::from("1: hi"),
String::from("1: hello"),
String::from("1: ha "),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_millis(1));
}
});
for received in rx {
println!("{}",received);
}
}
通过共享实现并发
多个线程可以同时访问同一个线程。使用mutex来允许一个线程来访问数据。

多线程共享Mutex
多线程的多重所有权问题。
Arc<T>
和 Rc<T>
类似,可以用于并发场景。A: atomic。
use std::{sync::{Mutex, Arc}, 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!("{:?}", counter); // Mutex { data: 10, poisoned: false, .. }
}
Send和Sync trait
Send: 允许线程间转移所有权。
Sync: 可以安全被多个线程引用。
面向对象
满足了面向对象的定义,如struct,impl。
Rust没有继承
使用继承的原因:
- 代码复用
- Rust默认trait方法来实现代码共享
- 多态
- Rust泛型和trait约束。
使用Triat对象存储不同类型的值
trait用来定义共有方法。
模式匹配
模式是Rust的一种特殊语法,用于匹配复杂和简单类型的结构。
能够匹配任何可能传递的值得模式:无可辩驳的
let x = 5;
对某些可能的值无法进行匹配的模式:可辩驳的
if let Some(x) = a_value
Unsafe Rust
没有强制内存安全保证。
存在原因:
- 静态分析保守,使用unsafe自担风险
- 计算机硬件本身不安全,Rust需要进行底层编程
unsafe并没有关闭借用检查。
unsafe可执行的四个动作:
- 解引用原始指针
- 调用unsafe函数
- 访问或修改可变的静态变量
- 实现unsafe trait
解引用原始指针:
Raw pointer
不同于引用,原始指针
- 允许为null
- 不实现自动清理
- 不保证指向合理内存
- 允许不可变和可变指针或多个可变指针忽略借用规则
fn main() {
let mut num = 45;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("{}", *r1); // 45
println!("{}", *r2); // 45
}
}
外部函数:

改变静态变量:
static mut COUNTER: u32 = 0;
// 静态变量,声明要标注类型,只能'static生命周期,无需显示标注
// 有固定的内存地址。
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("{}",COUNTER);
}
}
关联类型
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
高级类型
!
Never类型,无法产生可供返回的值。
例如:
let guess = "";
loop {
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
}
或者
loop {
print!("forever");
}
永远不会终止,返回的就是never类型。
动态大小
运行时才能确定字符串的长度。
引用、trait对象、sized triat。
函数指针
fn main() {
let answer = do_twice(add_one, 5);
println!("answer: {}", answer);
}
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
宏
原理比较复杂,用到再说。
macro
指一组相关特性的集合称谓。
- 使用macro_rules!构建的声明宏
- 三种过程宏
- 自定义#[derive]宏
- 类似属性的宏,可以在任何条目上添加自定义属性
- 类似函数的宏,看起来像函数调用
函数和宏的区别:
- 宏可以编写生成其他代码的代码
- 函数定义签名要声明参数个数和类型,宏可处理可变的参数
- 编译器在解释代码前会展开宏
- 宏的定义比函数复杂,难以阅读理解。