多线程

如果你使用多个线程,你可以同时做很多事情。现代计算机有一个以上的核心,所以它们可以同时做多件事情,Rust让你使用它们。Rust使用的线程被称为 "OS线程"。OS线程意味着操作系统在不同的核上创建线程。(其他一些语言使用 "green threads",功能较少)

你用std::thread::spawn创建线程,然后用一个闭包来告诉它该怎么做。线程很有趣,因为它们同时运行,你可以测试它,看看会发生什么。下面是一个简单的例子。

fn main() { std::thread::spawn(|| { println!("I am printing something"); }); }

如果你运行这个,每次都会不一样。有时会打印,有时不会打印(这也取决于你的电脑速度)。这是因为有时main()在线程完成之前就完成了。而当main()完成后,程序就结束了。这在for循环中更容易看到。

fn main() { for _ in 0..10 { // set up ten threads std::thread::spawn(|| { println!("I am printing something"); }); } // Now the threads start. } // How many can finish before main() ends here?

通常在main结束之前,大约会打印出四条线程,但总是不一样。如果你的电脑速度比较快,那么可能就不会打印了。另外,有时线程会崩溃。

thread 'thread 'I am printing something thread '<unnamed><unnamed>thread '' panicked at '<unnamed>I am printing something ' panicked at 'thread '<unnamed>cannot access stdout during shutdown' panicked at '<unnamed>thread 'cannot access stdout during shutdown

这是在程序关闭时,线程试图做一些正确的事情时出现的错误。

你可以给电脑做一些事情,这样它就不会马上关闭了。

fn main() { for _ in 0..10 { std::thread::spawn(|| { println!("I am printing something"); }); } for _ in 0..1_000_000 { // make the program declare "let x = 9" one million times // It has to finish this before it can exit the main function let _x = 9; } }

但这是一个让线程有时间完成的愚蠢方法。更好的方法是将线程绑定到一个变量上。如果你加上 let,你就能创建一个 JoinHandle。你可以在spawn的签名中看到这一点:

pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static,

(f是闭包--我们将在后面学习如何将闭包放入我们的函数中)

所以现在我们每次都有JoinHandle

fn main() { for _ in 0..10 { let handle = std::thread::spawn(|| { println!("I am printing something"); }); } }

handle现在是JoinHandle。我们怎么处理它呢?我们使用一个叫做 .join() 的方法。这个方法的意思是 "等待所有线程完成"(它等待线程加入它)。所以现在只要写handle.join(),它就会等待每个线程完成。

fn main() { for _ in 0..10 { let handle = std::thread::spawn(|| { println!("I am printing something"); }); handle.join(); // Wait for the threads to finish } }

现在我们就来了解一下三种类型的闭包。这三种类型是

  • FnOnce: 取整个值
  • FnMut: 取一个可变引用
  • Fn: 取一个普通引用

如果可以的话,闭包会尽量使用Fn。但如果它需要改变值,它将使用 FnMut,而如果它需要取整个值,它将使用 FnOnceFnOnce是个好名字,因为它解释了它的作用:它取一次值,然后就不能再取了。

下面是一个例子。

fn main() { let my_string = String::from("I will go into the closure"); let my_closure = || println!("{}", my_string); my_closure(); my_closure(); }

String没有实现Copy,所以my_closure()Fn: 它拿到一个引用

如果我们改变my_string,它变成FnMut

fn main() { let mut my_string = String::from("I will go into the closure"); let mut my_closure = || { my_string.push_str(" now"); println!("{}", my_string); }; my_closure(); my_closure(); }

这个打印:

I will go into the closure now I will go into the closure now now

而如果按值获取,则是FnOnce

fn main() { let my_vec: Vec<i32> = vec![8, 9, 10]; let my_closure = || { my_vec .into_iter() // into_iter takes ownership .map(|x| x as u8) // turn it into u8 .map(|x| x * 2) // multiply by 2 .collect::<Vec<u8>>() // collect into a Vec }; let new_vec = my_closure(); println!("{:?}", new_vec); }

我们是按值取的,所以我们不能多跑my_closure()次。这就是名字的由来。

那么现在回到线程。让我们试着从外部引入一个值:

fn main() { let mut my_string = String::from("Can I go inside the thread?"); let handle = std::thread::spawn(|| { println!("{}", my_string); // ⚠️ }); handle.join(); }

编译器说这个不行。

error[E0373]: closure may outlive the current function, but it borrows `my_string`, which is owned by the current function --> src\main.rs:28:37 | 28 | let handle = std::thread::spawn(|| { | ^^ may outlive borrowed value `my_string` 29 | println!("{}", my_string); | --------- `my_string` is borrowed here | note: function requires argument type to outlive `'static` --> src\main.rs:28:18 | 28 | let handle = std::thread::spawn(|| { | __________________^ 29 | | println!("{}", my_string); 30 | | }); | |______^ help: to force the closure to take ownership of `my_string` (and any other referenced variables), use the `move` keyword | 28 | let handle = std::thread::spawn(move || { | ^^^^^^^

这条信息很长,但很有用:它说到use the `move` keyword。问题是我们可以在线程使用my_string时对它做任何事情,但线程并不拥有它。这将是不安全的。

让我们试试其他行不通的东西。

fn main() { let mut my_string = String::from("Can I go inside the thread?"); let handle = std::thread::spawn(|| { println!("{}", my_string); // now my_string is being used as a reference }); std::mem::drop(my_string); // ⚠️ We try to drop it here. But the thread still needs it. handle.join(); }

所以你要用move来取值,现在安全了:

fn main() { let mut my_string = String::from("Can I go inside the thread?"); let handle = std::thread::spawn(move|| { println!("{}", my_string); }); std::mem::drop(my_string); // ⚠️ we can't drop, because handle has it. So this won't work handle.join(); }

所以我们把std::mem::drop删掉,现在就可以了。handlemy_string,我们的代码就安全了。

fn main() { let mut my_string = String::from("Can I go inside the thread?"); let handle = std::thread::spawn(move|| { println!("{}", my_string); }); handle.join(); }

所以只要记住:如果你在线程中需要一个来自线程外的值,你需要使用move