标准库中的基本线程支持允许创建的线程比创建它们的线程寿命更长;这是好事!但是,如果您将对堆栈分配的变量的引用传递给这些线程之一,则无法保证该变量在线程执行时仍然有效。在其他语言中,这将允许线程访问无效内存,从而产生一堆内存安全问题。
一种解决方案是作用域线程— 保证在父线程退出之前退出的线程。这些可以确保父线程中的堆栈变量在线程的整个持续时间内可用。
生锈 1.63
std::thread::scope https://doc.rust-lang.org/beta/std/thread/fn.scope.html时隔 7 年之后回归稳定的 Rust (removal https://github.com/rust-lang/rust/commit/6399bb425b3a82111cd554737f194c95b8f6bad5, return https://github.com/rust-lang/rust/issues/93203).
use std::{thread, time::Duration};
fn main() {
let mut vec = vec![1, 2, 3, 4, 5];
thread::scope(|scope| {
for e in &mut vec {
scope.spawn(move || {
thread::sleep(Duration::from_secs(1));
*e += 1;
});
}
});
println!("{:?}", vec);
}
早期的 Rust 版本或当您需要更多控制时
横梁
我们不仅限于标准库;作用域线程的流行板条箱是横梁 https://crates.io/crates/crossbeam:
use crossbeam; // 0.6.0
use std::{thread, time::Duration};
fn main() {
let mut vec = vec![1, 2, 3, 4, 5];
crossbeam::scope(|scope| {
for e in &mut vec {
scope.spawn(move |_| {
thread::sleep(Duration::from_secs(1));
*e += 1;
});
}
})
.expect("A child thread panicked");
println!("{:?}", vec);
}
rayon
还有像这样的箱子rayon https://crates.io/crates/rayon它抽象了“线程”的低级细节,但允许您实现您的目标:
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; // 1.0.3
use std::{thread, time::Duration};
fn main() {
let mut vec = vec![1, 2, 3, 4, 5];
vec.par_iter_mut().for_each(|e| {
thread::sleep(Duration::from_secs(1));
*e += 1;
});
println!("{:?}", vec);
}
关于示例
每个示例都会生成多个线程,并在没有锁定的情况下就地改变本地向量Arc
,并且没有克隆。请注意,突变有一个sleep
调用以帮助验证调用是否并行发生。
您可以扩展示例以共享对实现的任何类型的引用Sync https://doc.rust-lang.org/std/marker/trait.Sync.html, 比如一个Mutex
or an Atomic*
。然而,使用这些会引入锁定。
客户端需要将连接装箱Arc
当代码在库内部时是并行化的
也许你可以更好地隐藏你的并行性?您可以接受记录器,然后将其包装在一个Arc
/ Mutex
在将其交给您的线程之前?