Skip to content

Commit

Permalink
Fix some typo about drop and wrap... for all makrdown (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
fwqaaq authored Jul 8, 2023
1 parent 76bc298 commit c2aad73
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 88 deletions.
48 changes: 24 additions & 24 deletions 1_Basic_of_Rust_Concurrency.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions 2_Atomics.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

但是,在我们研究不同原子操作之前,我们需要简要谈谈叫做*内存排序*[^1]的概念:

每一个原子操作都采用了 `std::sync::atomic::Ordering` 类型的参数,这决定了我们对操作相对排序的保证。保证最少的简单变体是 `Relaxed``Relaxed` 只保证在单个原子变量中的一致性,但是在不同变量的相对操作顺序没有任何承诺。
每一个原子操作都接收 `std::sync::atomic::Ordering` 类型的参数,这决定了我们对操作相对排序的保证。保证最少的简单变体是 `Relaxed``Relaxed` 只保证在单个原子变量中的一致性,但是在不同变量的相对操作顺序没有任何承诺。

这意味着两个线程可能看到不同变量的操作以不同的顺序下发生。例如,如果一个线程首先写入一个变量,然后非常快速的写入第二个变量,另一个线程可能看见这以相反的顺序发生。

Expand All @@ -31,7 +31,7 @@ impl AtomicI32 {
}
```

load 方法以原子方式加载存储在原子变量中的值,并且 store 方法原子方式存储新值。注意,store 方法采用共享应用`&T`),而不是独占引用(`&mut T`),即使它修改了值。
load 方法以原子方式加载存储在原子变量中的值,并且 store 方法原子方式存储新值。注意,store 方法接收共享应用`&T`),而不是独占引用(`&mut T`),即使它修改了值。

让我们来看看这两种方式的使用示例。

Expand Down
4 changes: 2 additions & 2 deletions 3_Memory_Ordering.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ fn main() {
}
```

正如我们在[第二章“「比较并交换」操作”](./2_Atomics.md#比较并交换操作)简要地所见,「比较并交换」采用两个内存排序参数:一个用于比较成功且 store 发生的情况,一个用于比较失败且 `store` 没有发生的情况。在 f 中,我们试图去改变 `LOCKED` 的值从 false 到 true,并且只有在成功的情况下才能访问 DATA。所以,我们仅关心成功的内存排序。如果 `compare_exchange` 操作失败,那一定是因为 `LOCKED` 已经设置为 true,在这种情况下 f 不会做任何事情。这与常规 mutex 上的 `try_lock` 操作相匹配。
正如我们在[第二章“「比较并交换」操作”](./2_Atomics.md#比较并交换操作)简要地所见,「比较并交换」接收两个内存排序参数:一个用于比较成功且 store 发生的情况,一个用于比较失败且 `store` 没有发生的情况。在 f 中,我们试图去改变 `LOCKED` 的值从 false 到 true,并且只有在成功的情况下才能访问 DATA。所以,我们仅关心成功的内存排序。如果 `compare_exchange` 操作失败,那一定是因为 `LOCKED` 已经设置为 true,在这种情况下 f 不会做任何事情。这与常规 mutex 上的 `try_lock` 操作相匹配。

> 观察力强的读者可能已经注意到,「比较并交换」操作也可能是交换操作,因为在已锁定时将 true 替换为 true 不会改变代码的正确性:
>
Expand Down Expand Up @@ -401,7 +401,7 @@ fn get_data() -> &'static Data {
如果我们以 acquire-load 操作从 PTR 得到的指针是非空的,我们假设它指向已初始化的数据,并构建对该数据的引用。

然而,如果它仍然为空,我们会生成新数据,并使用 `Box::new` 将其存储在新分配的内存中。然后,我们使用 `Box::into_raw` 将此 `Box` 转换为原始指针,因此我们可以尝试使用「比较并交换」操作将其存储到 PTR 中。如果另一个线程赢得初始化竞争,`compare_exchange` 将失败,因为 PTR 不再是空的。如果发生这种情况,我们将原始指针转回 Box,使用 `dro`p 来释放分配的内存,避免内存泄漏,并继续使用另一个线程存储在 PTR 中的指针。
然而,如果它仍然为空,我们会生成新数据,并使用 `Box::new` 将其存储在新分配的内存中。然后,我们使用 `Box::into_raw` 将此 `Box` 转换为原始指针,因此我们可以尝试使用「比较并交换」操作将其存储到 PTR 中。如果另一个线程赢得初始化竞争,`compare_exchange` 将失败,因为 PTR 不再是空的。如果发生这种情况,我们将原始指针转回 Box,使用 `drop` 来释放分配的内存,避免内存泄漏,并继续使用另一个线程存储在 PTR 中的指针。

在最后的不安全块中,关于安全性的注视表明我们的假设是指它指向的数据已经被初始化。注意,这包括对事情发生顺序的假设。为了确保我们的假设成立,我们使用 release 和 acquire 内存顺序来确保初始化数据实际上在创建对其的引用之前已经发生。

Expand Down
10 changes: 5 additions & 5 deletions 4_Building_Our_Own_Spin_Lock.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ unsafe impl<T> Sync for SpinLock<T> where T: Send {}

注意,我们并不需要去要求 T 是 `Sync`,由于我们的 `SpinLock<T>` 仅一次允许一个线程访问它保护的 T。只有当我们同时允许多个线程访问权限时,就像读写锁对 reader 所做的那样,我们(另外)才需要 `T: Sync`

下一步,现在我们的新函数需要采用一个 T 类型的值来初始化 `UnsafeCell`
下一步,现在我们的新函数需要接收一个 T 类型的值来初始化 `UnsafeCell`

```rust
impl<T> SpinLock<T> {
Expand Down Expand Up @@ -149,9 +149,9 @@ pub unsafe fn unlock(&self) {

## 使用锁守卫的安全接口

为了能够提供一个完全安全地接口,我们需要将解锁操作绑定到 `&mut T` 的末尾。我们可以通过将此引用包装成我们自己的类型来做到这一点,该类型的行为类似于引用,但也实现了 Drop trait,以便在它被 drop 时做一些事情
为了能够提供一个完全安全地接口,我们需要将解锁操作绑定到 `&mut T` 的末尾。我们可以通过将此引用包装成我们自己的类型来做到这一点,该类型的行为类似于引用,但也实现了 Drop trait,以便在它被丢弃时做一些事情

这一类型通常被称为 *guard*,因为它有效地守卫了锁的状态,并且对该状态负责,直到它被 drop
这一类型通常被称为 *guard*,因为它有效地守卫了锁的状态,并且对该状态负责,直到它被丢弃

我们的 `Guard` 类型将仅包含对 SpinLock 的引用,以便它既可以访问 UnsafeCell,也可以稍后重置 AtomicBool:

Expand Down Expand Up @@ -257,7 +257,7 @@ fn main() {

上面的程序展示了我们的 `SpinLock` 是多么容易使用。多亏了 `Deref``DerefMut`,我们可以直接在 guard 上调用 `Vec::push` 方法。多亏了 `Drop`,我们不必担心解锁。

通过调用 `drop(g)` 来 drop guard,也可以明确地解锁。如果你尝试过早地解锁,你将看见 guard 正在做它的工作时,发生编译器错误。例如,如果你在两个 `push(2)` 行之间插入 `drop(g);`,第二个 push 将无法编译,因为你此时已经 drop g 了:
通过调用 `drop(g)` 来丢弃 guard,也可以明确地解锁。如果你尝试过早地解锁,你将看见 guard 正在做它的工作时,发生编译器错误。例如,如果你在两个 `push(2)` 行之间插入 `drop(g);`,第二个 push 将无法编译,因为你此时已经丢弃 g 了:

```txt
error[E0382]: borrow of moved value: `g`
Expand All @@ -281,7 +281,7 @@ error[E0382]: borrow of moved value: `g`
* *Acquire**Release* 内存排序对这个用例是极合适的。
* 当做出必要的未检查的假设以避免未定义的行为时,可以通过将函数标记为不安全来将责任转移到调用者。
* `Deref``DerefMut` trait 可用于使类型像引用一样,透明地提供对另一个对象的访问。
* `Drop` trait 可以用于在对象被 drop 时,做一些事情,例如当它超出作用域或者它被传递给 `drop()`
* `Drop` trait 可以用于在对象被丢弃时,做一些事情,例如当它超出作用域或者它被传递给 `drop()`
* *锁 guard* 是一种特殊类型的有用设计模式,它被用于表示对锁定的锁的(安全)访问。由于 `Deref` trait,这种类型通常与引用的行为相似,并通过 `Drop` trait 实现自动解锁。

<p style="text-align: center; padding-block-start: 5rem;">
Expand Down
22 changes: 11 additions & 11 deletions 5_Building_Our_Own_Channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

## 一个简单的以 mutex 为基础的 Channel

一个基础的 channel 实现并不需要任何关于原子的知识。我们可以采用 `VecDeque`,它根本上是一个 `Vec`,允许在两端高效的添加和移除元素,并使用 Mutex 保护它,以允许多个线程访问。然后,我们使用 `VecDeque` 作为已发送但尚未接受数据的消息队列。任何想要发送消息的线程只需要将其添加到队列的末尾,而任何想要接受消息的线程只需从队列的前端删除一个消息。
一个基础的 channel 实现并不需要任何关于原子的知识。我们可以接收 `VecDeque`,它根本上是一个 `Vec`,允许在两端高效的添加和移除元素,并使用 Mutex 保护它,以允许多个线程访问。然后,我们使用 `VecDeque` 作为已发送但尚未接受数据的消息队列。任何想要发送消息的线程只需要将其添加到队列的末尾,而任何想要接受消息的线程只需从队列的前端删除一个消息。

还有一件事需要补充,用于使接收操作阻塞:Condvar(参见[第一章“条件变量”](./1_Basic_of_Rust_Concurrency.md#条件变量)),以通知等待接收者新的消息。

Expand Down Expand Up @@ -61,7 +61,7 @@ impl<T> Channel<T> {

channel 的各种用例几乎是无止尽的。然而,在本章的剩余部分,我们将专注于一种特定类型的用例:恰好从一个线程向另一个线程发送一条消息。为此类用例设计的 channel 通常被称为 *一次性*(one-shot)channel。

我们采用上述基于 `Mutex<VecDeque>` 的实现,并且将 `VecDeque` 替换为 `Option`,从而将队列的容量减小到恰好一个消息。这样可以避免内存分配,但是仍然会有使用 Mutex 的一些缺点。我们可以通过使用原子操作从头构建我们自己的一次性 channel 来避免这个问题。
我们接收上述基于 `Mutex<VecDeque>` 的实现,并且将 `VecDeque` 替换为 `Option`,从而将队列的容量减小到恰好一个消息。这样可以避免内存分配,但是仍然会有使用 Mutex 的一些缺点。我们可以通过使用原子操作从头构建我们自己的一次性 channel 来避免这个问题。

首先,让我们构建一个最小化的一次性 channel 实现,不需要考虑它的接口。在本章的稍后,我们将探索如何改进其接口以及如何与 Rust 类型相结合,为 channel 的用于提供愉快的体验。

Expand Down Expand Up @@ -137,7 +137,7 @@ impl<T> Channel<T> {

多次调用 send 可能会导致数据竞争,因为第二个发送者在接收者尝试读取第一条消息时可能正在覆盖数据。即使接收操作得到了正确的同步,从多个线程调用 send 可能会导致两个线程尝试同时写入 cell,再次导致数据竞争。此外,多次调用 `receive` 会导致获取两个消息的副本,即使 T 不实现 `Copy` 并且因此不能安全地进行复制。

更微妙的问题是我们的通道缺乏 `Drop` 实现。`MaybeUninit` 类型不会跟踪它是否已经初始化,因此在被 drop 时不会自动 drop 其内容。这意味着如果发送了一条消息但从未被接收,该消息将永远不会被 drop。这不是不正确的,但仍然是要避免。在 Rust 中,泄漏被普遍认为是安全的,但通常只有作为另一个泄漏的结果才是可接受的。例如,泄漏 Vec 也会泄漏其内容,但正常使用 Vec 不会导致任何泄漏。
更微妙的问题是我们的通道缺乏 `Drop` 实现。`MaybeUninit` 类型不会跟踪它是否已经初始化,因此它在被丢弃时不会自动丢弃其内容。这意味着如果发送了一条消息但从未被接收,该消息将永远不会被丢弃。这不是不正确的,但仍然是要避免。在 Rust 中,泄漏被普遍认为是安全的,但通常只有作为另一个泄漏的结果才是可接受的。例如,泄漏 Vec 也会泄漏其内容,但正常使用 Vec 不会导致任何泄漏。

由于我们让用户对一切负责,不幸的事故只是时间问题。

Expand Down Expand Up @@ -230,11 +230,11 @@ impl<T> Channel<T> {

我们可以为原子 swap 操作使用 relaxed 内存排序,因为 `in_use`*总修改顺序*(参见[第三章“Relaxed 排序”](./3_Memory_Ordering.md#relaxed-排序))保证了在 in_use 上只会有一个 swap 操作返回的 false,而这是 send 方法尝试访问 cell 的唯一情况。

现在我们拥有了一个完全安全的接口,尽管还有一个问题未解决。最后一个问题出现在发送一个永远不会被接收的消息时:它将从不会被 drop。虽然这不会导致未定义行为,并且在安全代码中是允许的,但确实应该避免这种情况。
现在我们拥有了一个完全安全的接口,尽管还有一个问题未解决。最后一个问题出现在发送一个永远不会被接收的消息时:它将从不会被丢弃。虽然这不会导致未定义行为,并且在安全代码中是允许的,但确实应该避免这种情况。

由于我们在 receive 方法中重置了 ready 标志,修复这个问题很容易:ready 标志指示是否在 cell 中尚未接受的消息需要被 drop
由于我们在 receive 方法中重置了 ready 标志,修复这个问题很容易:ready 标志指示是否在 cell 中尚未接受的消息需要被丢弃

在我们的 Channel 的 Drop 实现中,我们不需要使用一个原子操作去检查原子 ready 标识,因为只有在在完全由正在 drop 的线程拥有所有权,且没有任何未解除借用的情况下,才能 drop 一个对象。这意味着,我们可以使用 `AtomicBool::get_mut` 方法,它接受一个独占引用(`&mut self`),以证明原子访问是不必要的。对于 UnsafeCell 也是一样,通过 `UnsafeCell::get_mut` 方法来来获取独占引用。
在我们的 Channel 的 Drop 实现中,我们不需要使用一个原子操作去检查原子 ready 标识,因为只有对象完全被正在丢弃它的线程所拥有的时候,且没有任何未解除借用的情况下,才能丢弃一个对象。这意味着,我们可以使用 `AtomicBool::get_mut` 方法,它接受一个独占引用(`&mut self`),以证明原子访问是不必要的。对于 UnsafeCell 也是一样,通过 `UnsafeCell::get_mut` 方法来来获取独占引用。

使用它,这是我们完全安全且不泄漏的 channel 的最后一部分:

Expand Down Expand Up @@ -397,7 +397,7 @@ unsafe impl<T> Sync for Channel<T> where T: Send {}

注意,我们不再像我们之前 channel 实现中的那样,需要 `in_use` 原子布尔值。它仅通过 send 来检查它有没有被调用超过一次,现在通过类型系统静态地保证。

channel 函数去创建一个 channel 和一对 sender-receiver,它与我们之前的 `Channel::new` 函数类似,除了 Channel 包裹了一个 Arc,并将该 Arc 和其克隆包装在 Sender 和 Receiver 类型中:
channel 函数去创建一个 channel 和一对 sender-receiver,它与我们之前的 `Channel::new` 函数类似,除了将 Channel 包装在 Arc 中,也将该 Arc 和其克隆包装在 Sender 和 Receiver 类型中:

```rust
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
Expand Down Expand Up @@ -454,7 +454,7 @@ impl<T> Drop for Channel<T> {
}
```

`Sender<T>` 或者 `Receiver<T>` 被 drop 时`Arc<Channel<T>>` 的 Drop 实现将减少对共享分配的内存的引用计数。当 drop 到第二个时,计数达到 0,并且 `Channel<T>` 自身被 drop。这将调用我们上面的 Drop 实现,如果已发送但未收到消息,我们将 drop 该消息
`Sender<T>` 或者 `Receiver<T>` 被丢弃时`Arc<Channel<T>>` 的 Drop 实现将减少对共享分配的内存的引用计数。当丢弃到第二个时,计数达到 0,并且 `Channel<T>` 自身被丢弃。这将调用我们上面的 Drop 实现,如果已发送但未收到消息,我们将丢弃该消息

让我们尝试它:

Expand Down Expand Up @@ -554,9 +554,9 @@ impl<T> Channel<T> {

`split` 方法使用一个极其复杂的签名,值得好好观察。它通过一个独占引用独占地借用 `self`,但它分成了两个共享引用,包装在 Sender 和 Receiver 类型中。`'a` 生命周期清楚地表明,这两个对象借用了有限的生命周期的东西;在这种情况下,是 Channel 本身的生命周期。由于 Channel 是独占地借用,只要 Sender 或 Receiver 对象存在,调用者不能去借用或者移动它。

然而,一旦这些对象都不再存在,可变的借用就会过期,编译器会愉快地让 Channel 对象通过第二次调用 `split()` 再次被借用。尽管我们可以假设在 Sender 和 Receiver 存在时,不能再次调用 `split()`我们不能阻止在这些对象被 drop 或者遗忘后再次调用 `split()`。我们需要确保我们不能偶然地在 channel 已经有它的 ready 标志设置的情况下创建新的 Sender 或 Receiver 对象,因为这将打包阻止未定义行为的假设。
然而,一旦这些对象都不再存在,可变的借用就会过期,编译器会愉快地让 Channel 对象通过第二次调用 `split()` 再次被借用。尽管我们可以假设在 Sender 和 Receiver 存在时,不能再次调用 `split()`我们不能阻止在这些对象被丢弃或者遗忘后再次调用 `split()`。我们需要确保我们不能偶然地在 channel 已经有它的 ready 标志设置的情况下创建新的 Sender 或 Receiver 对象,因为这将打包阻止未定义行为的假设。

通过在 `split()` 中用新的空 channel 覆盖 `*self`,我们确保它在创建 Sender 和 Receiver 状态时处于预期状态。这也会在旧的 `*self` 上调用 Drop 实现,它将负责 drop 之前发送但从未接收的消息
通过在 `split()` 中用新的空 channel 覆盖 `*self`,我们确保它在创建 Sender 和 Receiver 状态时处于预期状态。这也会在旧的 `*self` 上调用 Drop 实现,它将负责丢弃之前发送但从未接收的消息

> 由于 split 的签名的生命周期来自 `self`,它可以被省略。上面片段的 `split` 签名与这个不太冗长的版本相同
>
Expand Down Expand Up @@ -726,7 +726,7 @@ fn main() {
* 一个简单、灵活但可能效率低下的 channel,只需一个 `Mutex``Condvar` 就很容易实现。
* *一次性*(one-shot)channel 是一个被设计仅发送一次信息的 channel。
* `MaybeUninit<T>` 类型可用于表示可能尚未初始化的 `T`。其接口大多不安全,使用户负责跟踪其是否已初始化,不要复制非 `Copy` 数据,并在必要时删除其内容。
* 不 drop 对象(也称为泄漏或者遗忘)是安全的,但如果没有充分理由而这样做,会被视为不良的做法。
* 不丢弃对象(也称为泄漏或者遗忘)是安全的,但如果没有充分理由而这样做,会被视为不良的做法。
* panic 是创建安全接口的重要工具。
* 按值获取一个非 Copy 对象可以用于阻止某个操作被重复执行。
* 独占借用和拆分借用是确保正确性的强大工具。
Expand Down
Loading

0 comments on commit c2aad73

Please sign in to comment.