diff --git a/1_Basic_of_Rust_Concurrency.md b/1_Basic_of_Rust_Concurrency.md index e0f56ec..78887ad 100644 --- a/1_Basic_of_Rust_Concurrency.md +++ b/1_Basic_of_Rust_Concurrency.md @@ -1,6 +1,6 @@ # 第一章:Rust 并发基础 -早在多核处理器司空见惯之前,操作系统就允许一台计算机运行多个程序。这是通过在进程之间快速切换来完成的,允许每个进程逐个地重重地取得一点进展。现在,几乎所有的电脑,甚至手机和手表都有着多核处理器,可以真正并行执行多个程序。 +早在多核处理器司空见惯之前,操作系统就允许一台计算机运行多个程序。这是通过在进程之间快速切换来完成的,允许每个进程逐个地逐次取得一点进展。现在,几乎所有的电脑,甚至手机和手表都有着多核处理器,可以真正并行执行多个程序。 操作系统尽可能的将进程之间隔离,允许程序完全意识不到其他线程在做什么的情况下做自己的事情。例如,在不先询问操作系统内核的情况下,一个进程通常不能获取其他进程的内存,或者以任意方式与之通信。 @@ -14,7 +14,7 @@ 每个程序都从一个线程开始:主(main)线程。该线程将执行你的 main 函数,并且如果需要,它可以用于产生更多线程。 -在 Rust 中,新线程使用来自标准库的 `std::thread::spawn` 函数产生。它接受一个参数:新线程执行的函数。一旦该函数停止,将立刻返回。 +在 Rust 中,新线程使用来自标准库的 `std::thread::spawn` 函数产生。它接受一个参数:新线程执行的函数。一旦该函数返回,线程就会停止。 让我们看一个示例: @@ -40,7 +40,7 @@ fn f() {

Thread ID

- Rust 标准库位每一个线程分配一个唯一的标识符。此标识符可以通过 Thread::id() 访问并且拥有 ThreadId 类型。除了复制 ThreadId 以及检查它们相等外,你也做不了什么。不能保证这些 ID 将会连续分配,只是每个线程都会有所不同。 + Rust 标准库位每一个线程分配一个唯一的标识符。此标识符可以通过 Thread::id() 访问并且拥有 ThreadId 类型。除了复制 ThreadId 以及检查它们是否相等外,你也做不了什么。不能保证这些 ID 将会连续分配,只是每个线程都会有所不同。
如果你运行我们上面的示例几次,你可能注意到输出在运行之间有所不同。一次特定的运行在机器上的输出: @@ -51,15 +51,15 @@ Hello from another thread! This is my thread id: ``` -惊讶地是,部分输出似乎失去了。 +惊讶的是,部分输出似乎丢失了。 -这里发生的情况是:新的线程结束执行它们的函数之前,主线程结束执行了主函数。 +这里发生的情况是:新的线程完成其函数的执行之前,主线程完成了主函数的执行。 -从主函数返回将退出整个程序,即使所有线程仍然在运行。 +从主函数返回将退出整个程序,即使其它线程仍然在运行。 在这个特定的示例中,在程序被主线程关闭之前,其中一个新的线程有足够的消息到达第二条消息的一半。 -如果我们想要在主函数返回之前,确保线程结束,我们可以通过 `join` 它们来等待。未来这样做,我们在 `spawn` 函数返回后使用 `JoinHandle`: +如果我们想要线程在主函数返回之前完成执行,我们可以通过 `join` 它们来等待。未来这样做,我们使用 `spawn` 函数返回的 `JoinHandle`: ```rust fn main() { @@ -75,7 +75,7 @@ fn main() { `.join()` 方法等待直到线程结束执行并且返回 `std::thread::Result`。如果线程由于 panic 不能成功地完成它的函数,这将包含 panic 消息。我们试图去处理这种情况,或者为 join panic 的线程调用 `.unwrap()` 去 panic。 -运行我们程序的这个版本,将不再导致截断的输出: +运行我们程序的这个版本,将不再导致输出被截断: ```txt Hello from the main thread. @@ -97,7 +97,7 @@ This is my thread id: ThreadId(3)

输出锁定

- println 宏使用 std::io::Stdout::lock() 去确保输出没有被中断。println!() 将等待直到任意并发地运行完成后,在写入输出。如果不是这样,我们可以得到更多的交叉输出: + println 宏使用 std::io::Stdout::lock() 去确保输出没有被中断。println!() 表达式将等待直到任意并发的表达式运行完成后,再写入输出。如果不是这样,我们可能得到更多的交错输出:
   Hello fromHello from another thread!
@@ -107,7 +107,7 @@ This is my thread id: ThreadId(3)
   id: ThreadId(3)
-与其将函数的名称传递给 `std::thread::spawn`,不如像我们上面的示例那样,传递一个*闭包*。这允许我们捕获值移动到新的线程: +与其将函数的名称传递给 `std::thread::spawn`(像我们上面的示例那样),不如传递一个*闭包*。这允许我们捕获值并移动到新的线程: ```rust let numbers = vec![1, 2, 3]; @@ -119,11 +119,11 @@ thread::spawn(move || { }).join().unwrap(); ``` -在这里,numbers 的所有权被转移到新产生的线程,因为我们使用了 `move` 闭包。如果我们没有使用 `move` 关键字,闭包将会通过引用捕获 numbers。这将导致一个编译器错误,因为新的线程超出变量的生命周期。 +在这里,numbers 的所有权被转移到新产生的线程,因为我们使用了 `move` 闭包。如果我们没有使用 `move` 关键字,闭包将会通过引用捕获 numbers。这将导致一个编译器错误,因为新的线程比变量的生命周期更长。 由于线程可能运行直到程序执行结束,因此产生的线程在它的参数类型上有 `'static` 生命周期绑定。换句话说,它只接受永久保留的函数。闭包通过引用捕获局部变量不能够永久保留,因为当局部变量不存在时,引用将变得无效。 -从线程中取回一个值,是从闭包中返回完成的。该返回值通过 `join` 方法返回的 `Result` 中获取: +从线程中取回一个值,是从闭包中返回值来完成的。该返回值可以通过 `join` 方法返回的 `Result` 中获取: ```rust let numbers = Vec::from_iter(0..=1000); @@ -149,14 +149,14 @@ println!("average: {average}");

std::thread::Builder 允许你在产生线程之前为新线程设置一些设置。你可以使用它为新线程配置栈大小并给新线程一个名字。线程的名字是可以通过 std::thread::current().name() 获得,这将在 panic 消息中可用,并在监控和大多数雕饰工具中可见。

-

此外,Builder 的产生函数返回一个 std::io::Result,允许你处理新线程失败的情况。如果操作系统内存不足,或者资源限制已经应用于你对程序,这是可能发生的。如果 std::thread::spawn 函数不能去产生一个新线程,它只会 panic。

+

此外,Builder 的产生函数返回一个 std::io::Result,允许你处理产生新线程失败的情况。如果操作系统内存不足,或者资源限制已经应用于你的程序,这是可能发生的。如果 std::thread::spawn 函数不能去产生一个新线程,它就会 panic。

-## 线程作用域 +## 作用域内的线程 如果我们确信生成的线程不会比某个范围存活更久,那么线程可以安全地借用哪些不会一直存在的东西,例如局部变量,只要它们比该范围活得更久。 -Rust 标准库提供了 `std::thread::scope` 去产生此类*线程作用域*。它允许我们产生不超过我们传递给该函数闭包的范围的线程,这使它可能安全地借用局部变量。 +Rust 标准库提供了 `std::thread::scope` 去产生此类*作用域内的线程*。它允许我们产生不超过我们传递给该函数闭包的范围的线程,这使它可能安全地借用局部变量。 它的工作原理最好使用一个示例来展示: @@ -215,7 +215,7 @@ error[E0499]: cannot borrow `numbers` as mutable more than once at a time

泄漏启示录

-

在 Rust 1.0 之前,标准库有一个函数叫做 std::thread::scoped,它将直接产生一个线程,就像 std::thread::spawn。它允许无 'static 的捕获,因为它返回的不是 JoinGuard,而是当被 drop 时 join 到线程的 JoinGuard。任意的借用数据仅需要比这个 JoinGuard 活得更久。只要 JoinGuard 在某个时候被 drop,这似乎是安全的。

+

在 Rust 1.0 之前,标准库有一个函数叫做 std::thread::scoped,它将直接产生一个线程,就像 std::thread::spawn。它允许无 'static 的捕获,因为它返回的不是 JoinHandle,而是当被 drop 时 join 到线程的 JoinGuard。任意的借用数据仅需要比这个 JoinGuard 活得更久。只要 JoinGuard 在某个时候被 drop,这似乎是安全的。

就在 Rust 1.0 发布之前,人们慢慢发现它似乎不能保证某些东西被 drop。有很多种方式没有 drop 它,例如创建一个引用计数节点的循环,可以忘记某些东西或者*泄漏*它。

@@ -226,11 +226,11 @@ error[E0499]: cannot borrow `numbers` as mutable more than once at a time ## 共享所有权以及引用计数 -目前,我们已经使用了 `move` 闭包([“Rust 中的线程”](#rust-中的线程))将值的所有权转移到线程并从生命周期较长的父线程借用数据([“线程作用域”](#线程作用域))。当两个线程之间共享数据,它们之间的任何一个线程都不能保证比另一个线程的生命周期长,那么它们都不能称为该数据的所有者。它们之间共享的任何数据都需要与最长生命周期的线程一样长。 +目前,我们已经使用了 `move` 闭包([“Rust 中的线程”](#rust-中的线程))将值的所有权转移到线程并从生命周期较长的父线程借用数据([作用域内的线程](#作用域内的线程))。当两个线程之间共享数据,它们之间的任何一个线程都不能保证比另一个线程的生命周期长,那么它们都不能称为该数据的所有者。它们之间共享的任何数据都需要与最长生命周期的线程一样长。 ### Static -有几种方式去创建不属于单线程的东西。最简单的方式是**静态**值,它由整个程序“拥有”,而不是单个线程。在以下示例中,这两个线程都可以获取 X,但是它们并不能拥有它: +有几种方式去创建不属于单线程的东西。最简单的方式是**静态**值,它由整个程序“拥有”,而不是单个线程。在以下示例中,这两个线程都可以获取 X,但是它们并不拥有它: ```rust static X: [i32; 3] = [1, 2, 3]; @@ -262,7 +262,7 @@ thread::spawn(move || dbg!(x)); ### 引用计数 -为了确保共享数据能够 drop 和取消分配,我们不能完全放弃它的所有权。相反,我们可以*分享所有权*。通过跟踪所有者的数量,我们确保仅当没有所有者时,才会丢弃该值。 +为了确保共享数据能够 drop 和释放内存,我们不能完全放弃它的所有权。相反,我们可以*分享所有权*。通过跟踪所有者的数量,我们确保仅当没有所有者时,才会丢弃该值。 Rust 标准库通过 `std::rc::Rc` 类型提供了该功能,它是“引用计数”(reference counted)的缩写。它与 `Box` 非常类似,唯一的区别是克隆它将不会分配任何新内存,而是增加存储在包含值旁边的计数器。原始的 `Rc` 和克隆的 `Rc` 将引用相同的内存分配;它们*共享所有权*。 @@ -397,7 +397,7 @@ let b = unsafe { a.get_unchecked(index) };

如果我们破坏了这个假设,例如,我们以等于 3 的索引运行,任何事情都可能发生。它可能导致读取 a 之后存储的任何内存内容。这可能导致程序崩溃。它可能会执行程序中完全无关的部分。它可能会引起各种糟糕的情况。

-

或许令人惊讶的是,[未定义行为甚至可以“时间回溯”,导致之前的代码出问题](https://en.wikipedia.org/wiki/Speculative_execution)」。要理解这种情况是如何发生的,想象我们上面的片段有一个 match 语句,如下:

+

或许令人惊讶的是,未定义行为甚至可以“时间回溯”,导致之前的代码出问题。要理解这种情况是如何发生的,想象我们上面的片段有一个 match 语句,如下:

match index {
    0 => x(),
diff --git a/2_Atomics.md b/2_Atomics.md
index c813682..18d0340 100644
--- a/2_Atomics.md
+++ b/2_Atomics.md
@@ -108,7 +108,7 @@ fn main() {
 }
 ```
 
-这次,我们使用一个[线程作用域](./1_Basic_of_Rust_Concurrency.md#线程作用域),它将自动地为我们处理线程的 join,并且也允许我们借用局部变量。
+这次,我们使用一个[作用域内的线程](./1_Basic_of_Rust_Concurrency.md#作用域内的线程),它将自动地为我们处理线程的 join,并且也允许我们借用局部变量。
 
 每次后台线程完成处理项时,它都会将处理的项目数量存储在 AtomicUsize 中。与此同时,主线程向用户显示该数字,告知该进度,大约每秒一次。一旦主线程看见所有 10 项已经被处理,它就会退出作用域,它会隐式地 join 后台线程,并且告知用户所有都完成。
 
diff --git a/README.md b/README.md
index fadb3f8..ec6279f 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 ## [第一章:Rust 并发基础](./1_Basic_of_Rust_Concurrency.md)
 
 * [Rust 中的线程](./1_Basic_of_Rust_Concurrency.md#rust-中的线程)
-* [线程作用域](./1_Basic_of_Rust_Concurrency.md#线程作用域)
+* [作用域内的线程](./1_Basic_of_Rust_Concurrency.md#作用域内的线程)
 * [共享所有权以及引用计数](./1_Basic_of_Rust_Concurrency.md#共享所有权以及引用计数)
   * [Static](./1_Basic_of_Rust_Concurrency.md#static)
   * [泄漏(Leak)](./1_Basic_of_Rust_Concurrency.md#泄漏leak)