Skip to content

fix Chapter 9 #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 28, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions 9_Building_Our_Own_Locks.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

* *wait(&AtomicU32, u32)*

该函数用于等待直到原子变量不再包含给定的值。如果原子变量中存储的值等于给定值,它将阻塞。当另一个线程修改了原子变量的值,该线程需要在统一原子变量上调用以下的任意一个唤醒函数,以将等待的线程从睡眠中唤醒。
该函数用于等待直到原子变量不再包含给定的值。如果原子变量中存储的值等于给定值,它将阻塞。当另一个线程修改了原子变量的值,该线程需要在同一原子变量上调用以下的任意一个唤醒函数,以将等待的线程从睡眠中唤醒。

该函数可能没有对应的唤醒操作,从而虚假地返回。因此,请确保在原子变量返回后检查其值,并在必要时重复 `wait()`。

Expand All @@ -34,7 +34,7 @@

为了使用 atomic-wait crate,在你的 `Cargo.toml` 中**增加** `atomic-wait="1"` 到 `[dependencies]`;或者运行 `cargo add atomic-wait@1`,这样也同样达到相同的目的。这三个函数在 crate 的根中定义,你可以使用 `atomic_wait::{wait, wake_one, wake_all};` 导入它们。

> 当你阅读到这篇文章时,该 crate 可能有后续的可用版本,但该章节进使用主版本为 1 的构建。后续的版本可能有不兼容的接口
> 当你阅读到这篇文章时,该 crate 可能有后续的可用版本,但该章节将使用主版本为 1 的构建。而后续的版本可能有不兼容的接口

现在,我们已经有基础的知识,让我们开始吧。

Expand Down Expand Up @@ -132,9 +132,9 @@ impl<T> Drop for MutexGuard<'_, T> {
}
```

唤醒一个线程就足够了,因为即使有多个线程在等待,也仅有其中的一个线程能够认领锁。锁定它的下一个线程将在锁定完成后唤醒另一个线程,以此类推。同时唤醒多个线程,只会让这些线程感到不满,浪费宝贵的处理器时间,因为其中除了一个幸运的线程能够获取锁,其它线程都会意识到自己失去机会后,从而再次进入休眠状态。
唤醒一个线程就足够了,因为即使有多个线程在等待,也仅有其中的一个线程能够认领锁。锁定它的下一个线程将在锁定完成后唤醒另一个线程,以此类推。同时唤醒多个线程,只会让这些线程感到不满,浪费宝贵的处理器时间,因为其中除了一个幸运的线程能够获取锁,其它线程都会在意识到自己失去机会后,从而再次进入休眠状态。

注意,不能保证我们唤醒的每一个线程都能抓住锁。另一个线程可能仍然在它有机会之前立刻抓住锁
注意,我们不能保证唤醒的每一个线程都能抓住锁。其它线程可能仍然在它有机会之前立刻抓住锁

这里做出的一个重要的观察是,如果没有等待和唤醒功能,这个 mutex 的实现在技术上仍然是正确的(即内存安全)。因为 `wait()` 操作可以虚假地唤醒,我们无法对他何时返回作出任何假设。我们仍然得去管理我们锁定原语的状态。如果我们移除等待和唤醒函数调用,我们的 mutex 将与我们的自旋锁状态基本相同。

Expand Down Expand Up @@ -198,7 +198,7 @@ impl<T> Drop for MutexGuard<'_, T> {

注意,将 state 设置回 0 后,它不再指示是否有任何其它的等待线程。唤醒的线程负责将 state 设置为 2,以确保没有任何其它的线程忘记。这是为什么在我们的 lock 函数中「比较并交换」操作不是我们 while 循环的一部分。

这确实以为这,每当线程在锁定时不得不 `wait()`,当解锁时,它将也调用 `wake_one()`。然而,更重要的是,*在未考虑的情况下*,线程不试图获取锁的理想状况下,完全地避免了 `wait()` 和 `wake_one()` 调用。
这确实意味着,每当线程在锁定时不得不 `wait()`,当解锁时,它将也调用 `wake_one()`。然而,更重要的是,*没有争议*的情况是,线程不试图同时获取锁的理想状况,这完全地避免了 `wait()` 和 `wake_one()` 调用。

图 9-1 展示了两个线程同时尝试锁定我们的 mutex 操作的情况下的 happens-before 关系。首先线程通过改变 state 从 0 到 1 锁定 mutex。此时,第二个线程将无法获取锁,并且在改变 state 从 1 到 2 后进入睡眠。当第一个线程解锁 mutex 时,它会交换 state 回 0。因为是 2,表示一个等待线程,它调用 `wake_one()` 来唤醒第二个线程。注意,我们不能依赖于唤醒和等待操作之间的任何 happens-before 关系。虽然唤醒操作可能是负责唤醒等待线程的操作,但 happens-before 关系是通过 `acquire` swap 操作建立的,观察 `release` swap 操作存储的值。

Expand Down Expand Up @@ -392,7 +392,7 @@ impl Condvar {

等待方法将接收 `MutexGuard` 作为参数,因为它表示已锁定 mutex 的证明。它将也返回 `MutexGuard`,因为它要确保在返回之前,再次锁定 mutex。

正如我们之前概述的那样,该方法将首先检查 counter 当前的值,然后再解锁 mutex。解锁 mutex 之后,如果 counter 仍未改变,它将继续等待,以确保我们不会失去任意信号。一下是作为代码的样子
正如我们之前概述的那样,该方法将首先检查 counter 当前的值,然后再解锁 mutex。解锁 mutex 之后,如果 counter 仍未改变,它将继续等待,以确保我们不会失去任意信号。以下是代码的内容

```rust
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
Expand Down