mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 06:17:49 +00:00
Optimize translation.
This commit is contained in:
@@ -95,13 +95,13 @@ translation_contributors = []
|
||||
|
||||
由于任务自行定义暂停点,它们不需要操作系统来保存其状态。相反,它们可以在暂停自己前精确保存恢复所需的状态,这通常会带来更好的性能表现。例如,一个刚刚完成复杂计算的任务可能只需要保存最终结果,而不再需要中间过程。
|
||||
|
||||
协作式多任务处理的编程语言级实现通常甚至能够在暂停前保存调用栈的必要部分。例如,Rust 的 async/await 实现会将所有仍被需要的局部变量存储在一个自动生成的结构体中(如下所示)通过在暂停前保存调用栈的相关部分,所有任务可以共享单个调用栈,这使得每个任务的内存消耗大幅降低。这使得创建任意数量的协作式任务并且不会耗尽内存成为可能。
|
||||
协作式多任务处理的编程语言级实现甚至能够在暂停前保存调用栈的必要部分。例如,Rust 的 async/await 实现会将所有仍被需要的局部变量存储在一个自动生成的结构体中(如后文所示)通过在暂停前保存调用栈的相关部分,所有任务可以共享单个调用栈,这使得每个任务的内存消耗大幅降低。从而实现创建任意数量的协作式任务并且不会耗尽内存。
|
||||
|
||||
#### 讨论
|
||||
|
||||
协作式多任务处理的缺点在于,一个不配合的任务可能会长时间占用处理器资源。因此,恶意或有缺陷的任务可能会阻止其他任务运行,并且会拖慢甚至阻塞整个系统。因此,协作式多任务处理应仅在所有任务都会协作的情况下使用。让操作系统依赖于任意用户级程序的协作并不是一个好主意。
|
||||
|
||||
然而,协作式多任务处理在性能和内存方面的显著优势,使其成为适合在程序内部使用的好方法,特别是与异步操作结合使用。操作系统内核作为与异步硬件交互的性能关键程序,采用协作式多任务处理似乎是一种实现并发的理想方式
|
||||
然而,协作式多任务处理在性能和内存方面的显著优势,使其成为适合在程序内部使用的好方法,特别是与异步操作结合使用。操作系统内核作为与异步硬件交互的性能关键程序,采用协作式多任务处理似乎是一种实现并发的理想方式。
|
||||
|
||||
## Rust 中的 Async/Await
|
||||
|
||||
@@ -109,7 +109,7 @@ Rust 语言为协作式多任务处理提供了一流的支持,其实现形式
|
||||
|
||||
### Futures
|
||||
|
||||
一个 _future_ 代表一个可能尚未就绪的值。例如,这个值可以是一个由其他任务计算得出的整数,或从网络下载的文件。与其等待直到该值可用,futures 使得程序可以继续执行,直到需要该值时再处理。
|
||||
一个 _future_ 代表一个可能尚未就绪的值。例如,这个值可以是一个由其他任务计算得出的整数,或从网络下载的文件。futures 使得程序可以继续执行,直到需要该值时再处理,而非在原地等待直到它可用。
|
||||
|
||||
#### 示例
|
||||
|
||||
@@ -119,7 +119,7 @@ futures 的概念可以通过一个小例子说明:
|
||||
|
||||
该序列图展示了一个 `main` 函数,它从文件系统中读取文件,然后调用 `foo` 函数。这个过程会重复两次:一次使用同步的 `read_file` 调用,另一次使用异步的 `async_read_file` 调用。
|
||||
|
||||
使用同步调用时, `main` 函数需要等待文件从文件系统中加载完成。只有这样它才能调用 `foo` 函数,而这又需要它再次等待结果。
|
||||
使用同步调用时, `main` 函数需要等待文件从文件系统中加载完成后才能调用 `foo` 函数。
|
||||
|
||||
通过异步的 `async_read_file` 调用,文件系统会直接返回一个 future 并在后台异步加载文件。这使得 `main` 函数能够更早地调用 `foo` ,然后 `foo` 会与文件加载并行运行。在这个例子中,文件加载甚至在 `foo` 返回前就完成了,因此 `main` 在 `foo` 返回后无需等待就能直接处理文件。
|
||||
|
||||
@@ -260,7 +260,7 @@ fn example(min_len: usize) -> impl Future<Output = String> {
|
||||
|
||||
([Try it on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=91fc09024eecb2448a85a7ef6a97b8d8))
|
||||
|
||||
这里我们读取 `foo.txt` 文件,然后使用 `then` 组合子根据文件内容链接第二个future。如果内容长度小于给定的 `min_len`,我们会读取另一个文件 `bar.txt` 并将其追加到 `content` ,否则仅返回 `foo.txt` 的内容。
|
||||
这里我们读取 `foo.txt` 文件,然后使用 `then` 组合器根据文件内容链接第二个future。如果内容长度小于给定的 `min_len`,我们会读取另一个文件 `bar.txt` 并将其追加到 `content` ,否则仅返回 `foo.txt` 的内容。
|
||||
|
||||
我们需要对传递给 `then` 的闭包使用 [move 关键字][`move` keyword],否则 `min_len` 中会出现生命周期错误。使用 [`Either`] 包装器的原因是 `if` 和 `else` 代码块必须始终保持相同的类型。由于我们在代码块中返回了不同的 future 类型,必须使用包装器类型将它们统一为单一类型。[`ready`] 函数将一个值包装成立刻可用的 future。这里需要该函数是因为 `Either` 包装器要求被包装的值必须实现 Future。
|
||||
|
||||
@@ -285,7 +285,7 @@ fn foo() -> impl Future<Output = u32> {
|
||||
}
|
||||
```
|
||||
|
||||
只有这个关键字本身并不显得那么有用。然而,在 `async` 函数内部,`await` 关键字可用于获取一个 future 的异步值:
|
||||
只有这个关键字本身看起来不太有用。然而,在 `async` 函数内部,`await` 关键字可用于获取一个 future 的异步值:
|
||||
|
||||
```rust
|
||||
async fn example(min_len: usize) -> String {
|
||||
@@ -477,7 +477,7 @@ ExampleStateMachine::End(_) => {
|
||||
|
||||
Futures 在返回 `Poll::Ready` 后不应再次轮询,所以在处于 `End` 状态时发生 `poll` 调用,则直接 panic。
|
||||
|
||||
我们现在已经了解了编译器生成的状态机及其对 Future 的实现 _可能_ 的样子。实际上,编译器是以另一种方式生成代码的。(如果你感兴趣的话:这个实现当前基于 [协程][_coroutines_],但这仅仅是个实现细节。)
|
||||
我们现在已经了解了编译器生成的状态机及其对 Future 的实现 _可能_ 的样子。实际上,编译器是以另一种方式生成代码的。(如果你感兴趣的话:这个实现当前基于 [协程][_coroutines_],但这仅仅是一种实现细节。)
|
||||
|
||||
[_coroutines_]: https://doc.rust-lang.org/stable/unstable-book/language-features/coroutines.html
|
||||
|
||||
@@ -609,7 +609,7 @@ println!("internal reference: {:p}", stack_value.self_ptr);
|
||||
|
||||
#### `Pin<Box<T>>` 与 `Unpin`
|
||||
|
||||
固定 pinning API 通过 [`Pin`] 包装类型以及 [`Unpin`] trait 提供了解决 `&mut T` 问题的方案。这些类型背后的理念是,将所有 `Pin` 中能获取包装值的 `&mut` 引用的方法(例如 [`get_mut`][pin-get-mut] 或 [`deref_mut`][pin-deref-mut]) 都限制在 `Unpin` trait 上使用。`Unpin` trait 是一个 [_auto trait_] ,除会自动为所有类型实现,除了那些明确选择不实现的类型。通过让自引用结构体不实现 `Unpin`,使得无法(安全地)从 `Pin<Box<T>>` 类型中获取它们的 `&mut T` ,从而保证它们内部的自我引用保持有效。
|
||||
固定 pinning API 通过 [`Pin`] 包装类型以及 [`Unpin`] trait 提供了解决 `&mut T` 问题的方案。这些类型背后的理念是,将所有 `Pin` 中能获取包装值的 `&mut` 引用的方法(例如 [`get_mut`][pin-get-mut] 或 [`deref_mut`][pin-deref-mut]) 都限制在 `Unpin` trait 上使用。`Unpin` trait 是一个 [_auto trait_] ,会自动为所有类型实现,除了那些明确选择不实现的类型。通过让自引用结构体不实现 `Unpin`,使得无法(安全地)从 `Pin<Box<T>>` 类型中获取它们的 `&mut T` ,从而保证它们内部的自引用保持有效。
|
||||
|
||||
[`Pin`]: https://doc.rust-lang.org/stable/core/pin/struct.Pin.html
|
||||
[`Unpin`]: https://doc.rust-lang.org/nightly/std/marker/trait.Unpin.html
|
||||
@@ -761,14 +761,14 @@ async fn write_file() {
|
||||
|
||||
### 协作式多任务处理?
|
||||
|
||||
在这篇文章的开头,我们讨论了抢占式和协作式多任务处理。虽然抢占式多任务依赖操作系统强制切换运行中的任务,而协作式多任务则要求任务通过定期执行 _yield_ 操作主动放弃 CPU 控制权。协作式方法的最大优势在于任务能够自行保存状态,从而实现更高效的上下文切换,并允许任务间共享同一个调用栈。
|
||||
在这篇文章的开头,我们讨论了抢占式和协作式多任务处理。抢占式多任务依赖操作系统强制切换运行中的任务,而协作式多任务则要求任务通过定期执行 _yield_ 操作主动放弃 CPU 控制权。协作式方法的最大优势在于任务能够自行保存状态,从而实现更高效的上下文切换,并允许任务间共享同一个调用栈。
|
||||
|
||||
虽然可能不太明显,但 futures 和 async/await 实际上是一种协作式多任务模式的实现:
|
||||
|
||||
* 每个添加到执行器的 future 本质上都是协作式任务。
|
||||
* 相对于使用显式的 yield 操作符,futures 通过 `Poll::Pending`(或在最后 `Poll::Ready`)放弃 CPU 核心的控制权。
|
||||
* 并没有谁要强制 futures 放弃 CPU。如果它们想,它们可以永不从 `poll` 中返回。例如通过无限循环。
|
||||
* 由于每个 future 都有能力阻断执行器中其他 futures 的执行,我们得首先相信它们是无恶意的。
|
||||
* 相对于使用显式的 yield 操作符,future 通过 `Poll::Pending`(或在最后 `Poll::Ready`)放弃 CPU 核心的控制权。
|
||||
* 并没有谁要强制 future 放弃 CPU。如果它们想,它们可以永不从 `poll` 中返回。例如通过无限循环。
|
||||
* 由于每个 future 都有能力阻断执行器中其他 future 的执行,我们得首先相信它们是无恶意的。
|
||||
* Future 内部存储了所有在下一次 `poll` 调用时继续执行所需的状态。使用 async/await 时,编译器会自动检测所有需要的变量并将它们存储在生成的状态机内部。
|
||||
* 仅保存继续执行所需的最小状态。
|
||||
* 由于 `poll` 方法在返回时会释放调用栈,这同一个栈可以用于轮询其他 future。
|
||||
|
||||
Reference in New Issue
Block a user