- Published on
🦀 简单讲讲 Rust 多线程中的引用安全
- Authors
- Name
- 阿森 Hansen
- 知识图谱位置:Rust-多线程
- 阅读对象:对 Rust 感兴趣的工程师
- 预备条件;Rust 所有权,Rust 闭包的基本概念
1 安全的变量:所有权和作用域是什么关系?
Rust 为了保证安全,要管理对变量的访问,而其他地方要访问这个变量,只能通过引用(有些地方也叫“借用”)。
变量的作用域就像一个个房子,一个变量只能存在于一个“房子”里,其他“房子”要使用这个变量,只能通过“引用”。例子中“作用域1”有变量的所有权,而“作用域2、3”只有变量的访问权,只能通过引用来访问
就像 Dota 中的幻影长矛手,放大招时只有一个本体,其他都是镜像。镜像就是对本体的引用。
2 安全的闭包:闭包如何访问外部变量?
要创建一个线程,我们一般会这么写:
use std::thread;
fn main() {
let v = vec![1, 2, 3];
// 创建一个线程
let handle = thread::spawn(move || { // 注意:这里使用了 move 关键字表示闭包要获取变量 v 的所有权!
// 闭包的内部(一个作用域)。。。
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
我们知道,一个闭包包含了一个作用域。其中,spawn()
函数中的参数就是一个闭包,前面的 move
说明闭包捕获了 v
的所有权。从此以后,v
只能在闭包内部使用,闭包外面不能再使用了。
如果在闭包之后访问 v
,编译器会报错。
2.1 闭包和外部作用域
首先介绍一下闭包。
闭包是一个轻量级的函数,而与函数不同的是,除了包含了一系列代码之外, 闭包还可以访问外部变量。
闭包内部访问外部,就会有所有权问题。
现在,是时候拿出变量访问的三板斧1了。
而与之对应的,闭包访问变量也有三种方式:
- 闭包从外部捕获变量的所有权
- 从外部获取不可变引用
- 从外部获取可变引用
3 多线程的引用安全:为什么要 move ?
多线程应该选择哪一种呢?当然是最安全的那种!也就是第一个:“从外部获取所有权后访问”。
子线程就像是主线程生的孩子,而孩子长大了总是要独立的。孩子长大了结婚要自己搬出去住,谁也拦不住。一样地,子线程一旦生成就会脱离主线程的掌控。
主线程生成一个子线程,但是主线程和子线程是独立运行的。各自的作用域也完全不同。
所以,Rust 为了保证最关键的“引用安全”,编译器会要求闭包函数捕获外部变量的所有权。
不信你可以尝试拿去 move
关键字,Rust 编译立即报错。
4 编程时,应该注意什么?
所以,在写 Rust 时,一定要先设计好再实现,否则就可能会遭到编译器的拒绝编译暴击,注意以下两点:
- 一定要想好哪些变量时要传给子线程的。因为,变量传递给子线程之后主线程就无法再访问了。
- 如果主线程和子线程之间要传递数据,可以使用 Arc 原子引用计数、Mutex 互斥锁或者 mpsc 多发单收通道。
关于如何再主线程和子线程之间传递数据,具体细节本文不具体涉及,有兴趣可以自己查资料看看。有机会以后写写文章介绍一下。
5 总结
这篇文章介绍了 Rust 多线程的实现方式以及如何 Rust 是如何保证线程的引用安全的。希望能够帮到你。
参考
1 我在这篇文章中提出了“变量访问的三板斧”:🦀 从 Rust 的引用看计算机的内存和数据安全 | 阿森的知识图谱
Using Threads to Run Code Simultaneously - The Rust Programming Language
This work is licensed under Creative Commons Attribution-NonCommercial 4.0 International