diff --git a/src/ch16-00-concurrency.md b/src/ch16-00-concurrency.md index 2f2358b..ecd5861 100644 --- a/src/ch16-00-concurrency.md +++ b/src/ch16-00-concurrency.md @@ -13,7 +13,7 @@ 如下是本章将要涉及到的内容: -- 如何创建线程来同时运行多段代码。 -- **消息传递**(_Message passing_)并发,其中通道(channel)被用来在线程间传递消息。 -- **共享状态**(_Shared state_)并发,其中多个线程可以访问同一片数据。 -- `Sync` 和 `Send` trait,将 Rust 的并发保证扩展到用户定义的以及标准库提供的类型中。 +* 如何创建线程来同时运行多段代码。 +* **消息传递**(_Message passing_)并发,其中通道(channel)被用来在线程间传递消息。 +* **共享状态**(_Shared state_)并发,其中多个线程可以访问同一片数据。 +* `Sync` 和 `Send` trait,将 Rust 的并发保证扩展到用户定义的以及标准库提供的类型中。 diff --git a/src/ch16-01-threads.md b/src/ch16-01-threads.md index 9c46da0..32fbc4f 100644 --- a/src/ch16-01-threads.md +++ b/src/ch16-01-threads.md @@ -1,7 +1,7 @@ ## 使用线程同时运行代码 > [ch16-01-threads.md](https://github.com/rust-lang/book/blob/main/src/ch16-01-threads.md)
-> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f +> commit 6b9eae8ce91dd0d94982795762d22077d372e90c 在大部分现代操作系统中,已执行程序的代码在一个 **进程**(_process_)中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。运行这些独立部分的功能被称为 **线程**(_threads_)。 @@ -13,17 +13,7 @@ Rust 尝试减轻使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。 -编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模型有时被称为 _1:1_,一个 OS 线程对应一个语言线程。 - -很多编程语言提供了自己特殊的线程实现。编程语言提供的线程被称为 **绿色**(_green_)线程,使用绿色线程的语言会在不同数量的 OS 线程的上下文中执行它们。为此,绿色线程模式被称为 _M:N_ 模型:`M` 个绿色线程对应 `N` 个 OS 线程,这里 `M` 和 `N` 不必相同。 - -每一个模型都有其优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时**(_Runtime_)是一个令人迷惑的概念,其在不同上下文中可能有不同的含义。 - -在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必须能够调用 C 语言,这点也是不能妥协的。 - -绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。因此,Rust 标准库只提供了 1:1 线程模型实现。由于 Rust 是较为底层的语言,如果你愿意牺牲性能来换取抽象,以获得对线程运行更精细的控制及更低的上下文切换成本,你可以使用实现了 M:N 线程模型的 crate。 - -现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。 +编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模型有时被称为 *1:1*,一个 OS 线程对应一个语言线程。Rust 标准库只提供了 1:1 线程实现;有一些 crate 实现了其他有着不同取舍的线程模型。 ### 使用 `spawn` 创建新线程 @@ -32,22 +22,7 @@ Rust 尝试减轻使用线程的负面影响。不过在多线程上下文中编 文件名: src/main.rs ```rust -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)); - } -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-01/src/main.rs}} ``` 示例 16-1: 创建一个打印某些内容的新线程,但是主线程打印其它内容 @@ -79,24 +54,7 @@ hi number 5 from the spawned thread! 文件名: src/main.rs ```rust -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(); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-02/src/main.rs}} ``` 示例 16-2: 从 `thread::spawn` 保存一个 `JoinHandle` 以确保该线程能够运行至结束 @@ -126,24 +84,7 @@ hi number 9 from the spawned thread! 文件名: src/main.rs ```rust -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)); - } - }); - - handle.join().unwrap(); - - for i in 1..5 { - println!("hi number {} from the main thread!", i); - thread::sleep(Duration::from_millis(1)); - } -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/no-listing-01-join-too-early/src/main.rs}} ``` 主线程会等待直到新建线程执行完毕之后才开始执行 `for` 循环,所以输出将不会交替出现,如下所示: @@ -168,7 +109,7 @@ hi number 4 from the main thread! ### 线程与 `move` 闭包 -`move` 闭包,我们曾在第十三章简要的提到过,其经常与 `thread::spawn` 一起使用,因为它允许我们在一个线程中使用另一个线程的数据。 +`move` 关键字经常用于传递给 `thread::spawn` 的闭包,因为闭包会获取从环境中取得的值的所有权,因此会将这些值的所有权从一个线程传送到另一个线程。在第十三章 [“闭包会捕获其环境”][capture] 部分讨论了闭包上下文中的 `move`。现在我们会更专注于 `move` 和 `thread::spawn` 之间的交互。 在第十三章中,我们讲到可以在参数列表前使用 `move` 关键字强制闭包获取其使用的环境值的所有权。这个技巧在创建新线程将值的所有权从一个线程移动到另一个线程时最为实用。 @@ -177,38 +118,15 @@ hi number 4 from the main thread! 文件名: src/main.rs ```rust,ignore,does_not_compile -use std::thread; - -fn main() { - let v = vec![1, 2, 3]; - - let handle = thread::spawn(|| { - println!("Here's a vector: {:?}", v); - }); - - handle.join().unwrap(); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-03/src/main.rs}} ``` 示例 16-3: 尝试在另一个线程使用主线程创建的 vector 闭包使用了 `v`,所以闭包会捕获 `v` 并使其成为闭包环境的一部分。因为 `thread::spawn` 在一个新线程中运行这个闭包,所以可以在新线程中访问 `v`。然而当编译这个例子时,会得到如下错误: -```text -error[E0373]: closure may outlive the current function, but it borrows `v`, -which is owned by the current function - --> src/main.rs:6:32 - | -6 | let handle = thread::spawn(|| { - | ^^ may outlive borrowed value `v` -7 | println!("Here's a vector: {:?}", v); - | - `v` is borrowed here - | -help: to force the closure to take ownership of `v` (and any other referenced -variables), use the `move` keyword - | -6 | let handle = thread::spawn(move || { - | ^^^^^^^ +```console +{{#include ../listings/ch16-fearless-concurrency/listing-16-03/output.txt}} ``` Rust 会 **推断** 如何捕获 `v`,因为 `println!` 只需要 `v` 的引用,闭包尝试借用 `v`。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓 `v` 的引用是否一直有效。 @@ -218,19 +136,7 @@ Rust 会 **推断** 如何捕获 `v`,因为 `println!` 只需要 `v` 的引用 文件名: src/main.rs ```rust,ignore,does_not_compile -use std::thread; - -fn main() { - let v = vec![1, 2, 3]; - - let handle = thread::spawn(|| { - println!("Here's a vector: {:?}", v); - }); - - drop(v); // oh no! - - handle.join().unwrap(); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-04/src/main.rs}} ``` 示例 16-4: 一个具有闭包的线程,尝试使用一个在主线程中被回收的引用 `v` @@ -240,11 +146,10 @@ fn main() { 为了修复示例 16-3 的编译错误,我们可以听取错误信息的建议: ```text -help: to force the closure to take ownership of `v` (and any other referenced -variables), use the `move` keyword +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword | 6 | let handle = thread::spawn(move || { - | ^^^^^^^ + | ++++ ``` 通过在闭包之前增加 `move` 关键字,我们强制闭包获取其使用的值的所有权,而不是任由 Rust 推断它应该借用值。示例 16-5 中展示的对示例 16-3 代码的修改,可以按照我们的预期编译并运行: @@ -252,37 +157,19 @@ variables), use the `move` keyword 文件名: src/main.rs ```rust -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(); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-05/src/main.rs}} ``` 示例 16-5: 使用 `move` 关键字强制获取它使用的值的所有权 那么如果使用了 `move` 闭包,示例 16-4 中主线程调用了 `drop` 的代码会发生什么呢?加了 `move` 就搞定了吗?不幸的是,我们会得到一个不同的错误,因为示例 16-4 所尝试的操作由于一个不同的原因而不被允许。如果为闭包增加 `move`,将会把 `v` 移动进闭包的环境中,如此将不能在主线程中对其调用 `drop` 了。我们会得到如下不同的编译错误: -```text -error[E0382]: use of moved value: `v` - --> src/main.rs:10:10 - | -6 | let handle = thread::spawn(move || { - | ------- value moved (into closure) here -... -10 | drop(v); // oh no! - | ^ value used here after move - | - = note: move occurs because `v` has type `std::vec::Vec`, which does - not implement the `Copy` trait +```console +{{#include ../listings/ch16-fearless-concurrency/output-only-01-move-drop/output.txt}} ``` Rust 的所有权规则又一次帮助了我们!示例 16-3 中的错误是因为 Rust 是保守的并只会为线程借用 `v`,这意味着主线程理论上可能使新建线程的引用无效。通过告诉 Rust 将 `v` 的所有权移动到新建线程,我们向 Rust 保证主线程不会再使用 `v`。如果对示例 16-4 也做出如此修改,那么当在主线程中使用 `v` 时就会违反所有权规则。 `move` 关键字覆盖了 Rust 默认保守的借用,但它不允许我们违反所有权规则。 现在我们对线程和线程 API 有了基本的了解,让我们讨论一下使用线程实际可以 **做** 什么吧。 + +[capture]: ch13-01-closures.html#闭包会捕获其环境 diff --git a/src/ch16-02-message-passing.md b/src/ch16-02-message-passing.md index c3ca06e..eebc9c5 100644 --- a/src/ch16-02-message-passing.md +++ b/src/ch16-02-message-passing.md @@ -1,9 +1,9 @@ ## 使用消息传递在线程间传送数据 > [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/main/src/ch16-02-message-passing.md)
-> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493 +> commit 24e275d624fe85af7b5b6316e78f8bfbbcac23e7 -一个日益流行的确保安全并发的方式是 **消息传递**(_message passing_),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](http://golang.org/doc/effective_go.html) 的口号:“不要通过共享内存来通讯;而是通过通讯来共享内存。”(“Do not communicate by sharing memory; instead, share memory by communicating.”) +一个日益流行的确保安全并发的方式是 **消息传递**(_message passing_),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](https://golang.org/doc/effective_go.html#concurrency) 的口号:“不要通过共享内存来通讯;而是通过通讯来共享内存。”(“Do not communicate by sharing memory; instead, share memory by communicating.”) Rust 中一个实现消息传递并发的主要工具是 **通道**(_channel_),Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。 @@ -16,11 +16,7 @@ Rust 中一个实现消息传递并发的主要工具是 **通道**(_channel_ 文件名: src/main.rs ```rust,ignore,does_not_compile -use std::sync::mpsc; - -fn main() { - let (tx, rx) = mpsc::channel(); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-06/src/main.rs}} ``` 示例 16-6: 创建一个通道,并将其两端赋值给 `tx` 和 `rx` @@ -34,17 +30,7 @@ fn main() { 文件名: src/main.rs ```rust -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(); - }); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-07/src/main.rs}} ``` 示例 16-7: 将 `tx` 移动到一个新建的线程中并发送 “hi” @@ -58,20 +44,7 @@ fn main() { 文件名: src/main.rs ```rust -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); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-08/src/main.rs}} ``` 示例 16-8: 在主线程中接收并打印内容 “hi” @@ -97,38 +70,15 @@ Got: hi 文件名: src/main.rs ```rust,ignore,does_not_compile -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(); - println!("val is {}", val); - }); - - let received = rx.recv().unwrap(); - println!("Got: {}", received); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-09/src/main.rs}} ``` 示例 16-9: 在我们已经发送到通道中后,尝试使用 `val` 引用 这里尝试在通过 `tx.send` 发送 `val` 到通道中之后将其打印出来。允许这么做是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们再次使用它之前就将其修改或者丢弃。其他线程对值可能的修改会由于不一致或不存在的数据而导致错误或意外的结果。然而,尝试编译示例 16-9 的代码时,Rust 会给出一个错误: -```text -error[E0382]: use of moved value: `val` - --> src/main.rs:10:31 - | -9 | tx.send(val).unwrap(); - | --- value moved here -10 | println!("val is {}", val); - | ^^^ value used here after move - | - = note: move occurs because `val` has type `std::string::String`, which does -not implement the `Copy` trait +```console +{{#include ../listings/ch16-fearless-concurrency/listing-16-09/output.txt}} ``` 我们的并发错误会造成一个编译时错误。`send` 函数获取其参数的所有权并移动这个值归接收者所有。这可以防止在发送后再次意外地使用这个值;所有权系统检查一切是否合乎规则。 @@ -139,32 +89,8 @@ not implement the `Copy` trait 文件名: src/main.rs -```rust -use std::thread; -use std::sync::mpsc; -use std::time::Duration; - -fn main() { - let (tx, rx) = mpsc::channel(); - - thread::spawn(move || { - let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("thread"), - ]; - - for val in vals { - tx.send(val).unwrap(); - thread::sleep(Duration::from_secs(1)); - } - }); - - for received in rx { - println!("Got: {}", received); - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-10/src/main.rs}} ``` 示例 16-10: 发送多个消息,并在每次发送后暂停一段时间 @@ -190,51 +116,8 @@ Got: thread 文件名: src/main.rs -```rust -# use std::thread; -# use std::sync::mpsc; -# use std::time::Duration; -# -# fn main() { -// --snip-- - -let (tx, rx) = mpsc::channel(); - -let tx1 = tx.clone(); -thread::spawn(move || { - let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("thread"), - ]; - - for val in vals { - tx1.send(val).unwrap(); - thread::sleep(Duration::from_secs(1)); - } -}); - -thread::spawn(move || { - let vals = vec![ - String::from("more"), - String::from("messages"), - String::from("for"), - String::from("you"), - ]; - - for val in vals { - tx.send(val).unwrap(); - thread::sleep(Duration::from_secs(1)); - } -}); - -for received in rx { - println!("Got: {}", received); -} - -// --snip-- -# } +```rust,noplayground +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-11/src/main.rs:here}} ``` 示例 16-11: 从多个生产者发送多个消息 diff --git a/src/ch16-03-shared-state.md b/src/ch16-03-shared-state.md index 9c42427..7940d0c 100644 --- a/src/ch16-03-shared-state.md +++ b/src/ch16-03-shared-state.md @@ -1,7 +1,7 @@ ## 共享状态并发 > [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/main/src/ch16-03-shared-state.md)
-> commit ef072458f903775e91ea9e21356154bc57ee31da +> commit 75b9d4a8dccc245e0343eb1480aa86f169043ea5 虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。再一次思考一下 Go 编程语言文档中口号的这一部分:“不要通过共享内存来通讯”(“do not communicate by sharing memory.”): @@ -31,18 +31,7 @@ 文件名: src/main.rs ```rust -use std::sync::Mutex; - -fn main() { - let m = Mutex::new(5); - - { - let mut num = m.lock().unwrap(); - *num = 6; - } - - println!("m = {:?}", m); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-12/src/main.rs}} ``` 示例 16-12: 出于简单的考虑,在一个单线程上下文中探索 `Mutex` 的 API @@ -64,28 +53,7 @@ fn main() { 文件名: src/main.rs ```rust,ignore,does_not_compile -use std::sync::Mutex; -use std::thread; - -fn main() { - let counter = Mutex::new(0); - let mut handles = vec![]; - - for _ in 0..10 { - 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()); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-13/src/main.rs}} ``` 示例 16-13: 程序启动了 10 个线程,每个线程都通过 `Mutex` 来增加计数器的值 @@ -96,73 +64,28 @@ fn main() { 之前提示过这个例子不能编译,让我们看看为什么! -```text -error[E0382]: use of moved value: `counter` - --> src/main.rs:9:36 - | -9 | let handle = thread::spawn(move || { - | ^^^^^^^ value moved into closure here, -in previous iteration of loop -10 | let mut num = counter.lock().unwrap(); - | ------- use occurs due to use in closure - | - = note: move occurs because `counter` has type `std::sync::Mutex`, -which does not implement the `Copy` trait +```console +{{#include ../listings/ch16-fearless-concurrency/listing-16-13/output.txt}} ``` 错误信息表明 `counter` 值在上一次循环中被移动了。所以 Rust 告诉我们不能将 `counter` 锁的所有权移动到多个线程中。让我们通过一个第十五章讨论过的多所有权手段来修复这个编译错误。 #### 多线程和多所有权 -在第十五章中,通过使用智能指针 `Rc` 来创建引用计数的值,以便拥有多所有者。让我们在这也这么做看看会发生什么。将示例 16-14 中的 `Mutex` 封装进 `Rc` 中并在将所有权移入线程之前克隆了 `Rc`。现在我们理解了所发生的错误,同时也将代码改回使用 `for` 循环,并保留闭包的 `move` 关键字: +在第十五章中,通过使用智能指针 `Rc` 来创建引用计数的值,以便拥有多所有者。让我们在这也这么做看看会发生什么。将示例 16-14 中的 `Mutex` 封装进 `Rc` 中并在将所有权移入线程之前克隆了 `Rc`。 文件名: src/main.rs ```rust,ignore,does_not_compile -use std::rc::Rc; -use std::sync::Mutex; -use std::thread; - -fn main() { - let counter = Rc::new(Mutex::new(0)); - let mut handles = vec![]; - - for _ in 0..10 { - let counter = Rc::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()); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-14/src/main.rs}} ``` 示例 16-14: 尝试使用 `Rc` 来允许多个线程拥有 `Mutex` 再一次编译并...出现了不同的错误!编译器真是教会了我们很多! -```text -error[E0277]: `std::rc::Rc>` cannot be sent between threads safely - --> src/main.rs:11:22 - | -11 | let handle = thread::spawn(move || { - | ^^^^^^^^^^^^^ `std::rc::Rc>` -cannot be sent between threads safely - | - = help: within `[closure@src/main.rs:11:36: 14:10 -counter:std::rc::Rc>]`, the trait `std::marker::Send` -is not implemented for `std::rc::Rc>` - = note: required because it appears within the type -`[closure@src/main.rs:11:36: 14:10 counter:std::rc::Rc>]` - = note: required by `std::thread::spawn` +```console +{{#include ../listings/ch16-fearless-concurrency/listing-16-14/output.txt}} ``` 哇哦,错误信息太长不看!这里是一些需要注意的重要部分:第一行错误表明 `` `std::rc::Rc>` cannot be sent between threads safely ``。编译器也告诉了我们原因 `` the trait bound `Send` is not satisfied ``。下一部分会讲到 `Send`:这是确保所使用的类型可以用于并发环境的 trait 之一。 @@ -171,7 +94,7 @@ is not implemented for `std::rc::Rc>` #### 原子引用计数 `Arc` -所幸 `Arc` **正是** 这么一个类似 `Rc` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性**(_atomic_),所以这是一个**原子引用计数**(_atomically reference counted_)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。 +所幸 `Arc` **正是** 这么一个类似 `Rc` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性**(_atomic_),所以这是一个 **原子引用计数**(_atomically reference counted_)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 [`std::sync::atomic`][atomic] 的文档来获取更多细节。目前我们只需要知道原子类就像基本类型一样可以安全的在线程间共享。 你可能会好奇为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用 `Arc` 实现?原因在于线程安全带有性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。 @@ -180,29 +103,7 @@ is not implemented for `std::rc::Rc>` 文件名: src/main.rs ```rust -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()); -} +{{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-15/src/main.rs}} ``` 示例 16-15: 使用 `Arc` 包装一个 `Mutex` 能够实现在多线程之间共享所有权 @@ -222,3 +123,5 @@ Result: 10 另一个值得注意的细节是 Rust 不能避免使用 `Mutex` 的全部逻辑错误。回忆一下第十五章使用 `Rc` 就有造成引用循环的风险,这时两个 `Rc` 值相互引用,造成内存泄漏。同理,`Mutex` 也有造成 **死锁**(_deadlock_) 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex` 和 `MutexGuard` 的 API 文档会提供有用的信息。 接下来,为了丰富本章的内容,让我们讨论一下 `Send`和 `Sync` trait 以及如何对自定义类型使用他们。 + +[`std::sync::atomic`]: https://doc.rust-lang.org/std/sync/atomic/index.html diff --git a/src/ch16-04-extensible-concurrency-sync-and-send.md b/src/ch16-04-extensible-concurrency-sync-and-send.md index 8ac631c..936e154 100644 --- a/src/ch16-04-extensible-concurrency-sync-and-send.md +++ b/src/ch16-04-extensible-concurrency-sync-and-send.md @@ -1,7 +1,7 @@ ## 使用 `Sync` 和 `Send` trait 的可扩展并发 > [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/main/src/ch16-04-extensible-concurrency-sync-and-send.md)
-> commit 426f3e4ec17e539ae9905ba559411169d303a031 +> commit a7a6804a2444ee05ff8b93f54973a9ce0f6511c1 Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。 @@ -9,7 +9,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 ** ### 通过 `Send` 允许在线程间转移所有权 -`Send` 标记 trait 表明类型的所有权可以在线程间传递。几乎所有的 Rust 类型都是`Send` 的,不过有一些例外,包括 `Rc`:这是不能 `Send` 的,因为如果克隆了 `Rc` 的值并尝试将克隆的所有权转移到另一个线程,这两个线程都可能同时更新引用计数。为此,`Rc` 被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价。 +`Send` 标记 trait 表明实现了 `Send` 的类型值的所有权可以在线程间传送。几乎所有的 Rust 类型都是`Send` 的,不过有一些例外,包括 `Rc`:这是不能 `Send` 的,因为如果克隆了 `Rc` 的值并尝试将克隆的所有权转移到另一个线程,这两个线程都可能同时更新引用计数。为此,`Rc` 被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价。 因此,Rust 类型系统和 trait bound 确保永远也不会意外的将不安全的 `Rc` 在线程间发送。当尝试在示例 16-14 中这么做的时候,会得到错误 `the trait Send is not implemented for Rc>`。而使用标记为 `Send` 的 `Arc` 时,就没有问题了。 @@ -17,7 +17,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 ** ### `Sync` 允许多线程访问 -`Sync` 标记 trait 表明一个实现了 `Sync` 的类型可以安全的在多个线程中拥有其值的引用。换一种方式来说,对于任意类型 `T`,如果 `&T`(`T` 的引用)是 `Send` 的话 `T` 就是 `Sync` 的,这意味着其引用就可以安全的发送到另一个线程。类似于 `Send` 的情况,基本类型是 `Sync` 的,完全由 `Sync` 的类型组成的类型也是 `Sync` 的。 +`Sync` 标记 trait 表明一个实现了 `Sync` 的类型可以安全的在多个线程中拥有其值的引用。换一种方式来说,对于任意类型 `T`,如果 `&T`(`T` 的不可变引用)是 `Send` 的话 `T` 就是 `Sync` 的,这意味着其引用就可以安全的发送到另一个线程。类似于 `Send` 的情况,基本类型是 `Sync` 的,完全由 `Sync` 的类型组成的类型也是 `Sync` 的。 智能指针 `Rc` 也不是 `Sync` 的,出于其不是 `Send` 相同的原因。`RefCell`(第十五章讨论过)和 `Cell` 系列类型不是 `Sync` 的。`RefCell` 在运行时所进行的借用检查也不是线程安全的。`Mutex` 是 `Sync` 的,正如 [“在线程间共享 `Mutex`”][sharing-a-mutext-between-multiple-threads] 部分所讲的它可以被用来在多线程中共享访问。 @@ -25,9 +25,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 ** 通常并不需要手动实现 `Send` 和 `Sync` trait,因为由 `Send` 和 `Sync` 的类型组成的类型,自动就是 `Send` 和 `Sync` 的。因为他们是标记 trait,甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。 -手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send` 和 `Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[The Rustonomicon] 中有更多关于这些保证以及如何维持他们的信息。 - -[the rustonomicon]: https://doc.rust-lang.org/stable/nomicon/ +手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send` 和 `Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[“The Rustonomicon”][nomicon] 中有更多关于这些保证以及如何维持他们的信息。 ## 总结 @@ -39,4 +37,5 @@ Rust 提供了用于消息传递的通道,和像 `Mutex` 和 `Arc` 这 接下来,让我们讨论一下当 Rust 程序变得更大时,有哪些符合语言习惯的问题建模方法和结构化解决方案,以及 Rust 的风格是如何与面向对象编程(Object Oriented Programming)中那些你所熟悉的概念相联系的。 -[sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads +[sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#在线程间共享-mutext +[nomicon]: https://doc.rust-lang.org/nomicon/index.html