Skip to content
New issue

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

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

Already on GitHub? # to your account

Review chapter 10 #51

Merged
merged 1 commit into from
Jul 9, 2023
Merged
Show file tree
Hide file tree
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
30 changes: 15 additions & 15 deletions 10_Ideas_and_Inspiration.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

![ ](https://github.com/fwqaaq/Rust_Atomics_and_Locks/raw/main/picture/raal_10in01.png)

信号量可以实现为用于计数器的 `Mutex<u32>` 以及用于等待操作的 `Condvar` 的组合。然而,有几种方式能更有效地实现它。更值得关注的是,在支持类 futex 操作([第八章“futex”](./8_Operating_System_Primitives.md#futex))的平台上,它可以作为单个 AtomicU32(或者甚至 AtomicU8)更高效地实现。
信号量可以实现为用于计数器的 `Mutex<u32>` 以及用于等待操作的 `Condvar` 的组合。然而,有几种方式能更有效地实现它。更值得关注的是,在支持类 futex 操作([第八章“futex”](./8_Operating_System_Primitives.md#futex))的平台上,可以使用单个 AtomicU32(或者甚至 AtomicU8)更高效地实现。

最大值为 1 的信号量又是被称为*二进制*信号量,它可以用作构建其他原语的基石。例如,它可以通过初始化计数器、使用锁定的 wait 操作以解锁的 signal 操作来做用于 mutex。通过在 0 处初始化它,它可以被用作信号,就像条件变量一样。例如,在标准库 `std::thread` 的 `park()` `unpark()` 函数可以实现与线程关联的二进制信号量上的 wait 和 signal 操作。
最大值为 1 的信号量又是被称为*二进制*信号量,它可以用作构建其他原语的基石。例如,它可以通过初始化计数器初始化为 1、使用锁定的 wait 操作以锁定以及使用 signal 操作以解锁,来用作 mutex。通过在 0 处初始化它,它也可以被用作信号,类似于条件变量。例如,在标准库 `std::thread` 的 `park()` `unpark()` 函数可以实现与线程关联的二进制信号量上的 wait 和 signal 操作。

> 注意,mutex 可以使用信号量来实现,而信号量可以使用 mutex(或者条件变量)来实现。建议避免使用基于 mutex 的信号量来实现基于信号量的 mutex,反之亦然。

Expand All @@ -31,17 +31,17 @@

如果你想要多个线程去(更多地)读和(少量地)更改一些数据,你可以使用 RwLock。当这些数据仅是单个整数时,你可以使用单个原子变量(例如 `AtomicU32`)去避免锁定,这样更有效。然而,对于巨大数据的分块,像有着很多字段的结构体,没有可用的原子类型允许对整个对象进行无锁原子操作。

就像计算机科学中的其他问题一样,该问题也可以通过**增加**间接的层来解决。你可以使用原子变量去存储一个指向它的指针,而不是结构体本身。这仍然不允许你以原子地方式修改整个结构体,但它允许你以原子地方式替换整个结构体,这差不多。
就像计算机科学中的其他问题一样,该问题也可以通过**增加**间接的层的方式来解决。你可以使用原子变量去存储一个指向它的指针,而不是结构体本身。这仍然不允许你以原子地方式修改整个结构体,但它允许你以原子地方式替换整个结构体,这差不多。

这种模式通常称为 `RCU`,代表“读取、复制、更新”,这些替换数据所需要的步骤。读取指针后,可以将结构体复制进新的内存分配中,无需担心其他线程即可进行修改。准备就绪后,可以使用比较并交换操作([第二章节的“比较并交换”操作](./2_Atomics.md#比较并交换操作))来更新原子指针,如果没有其他线程在此期间替换数据,这将成功。
这种模式通常称为 `RCU`,代表“读取、复制、更新”,这是替换数据所需要的步骤。读取指针后,可以将结构体复制进新的内存分配中,无需担心其他线程即可进行修改。准备就绪后,可以使用比较并交换操作([第二章节的“比较并交换”操作](./2_Atomics.md#比较并交换操作))来更新原子指针,如果没有其他线程在此期间替换数据,这将成功。

![ ](https://github.com/fwqaaq/Rust_Atomics_and_Locks/raw/main/picture/raal_10in02.png)

关于 RCU 模式最有趣的部分是最后一步,它没有首字母缩略的单词:重新分配旧数据(deallocating the old data)。成功更新后,如果其他线程在更新前读取指针,它们仍然可能读取旧副本。你必须等待所有这些线程的完成,才能重新分配旧副本。

对于这个问题有很多可能的解决方案,包括引用计数(例如 `Arc`)、泄漏内存(忽视问题)、垃圾收集、冒险指针[^2](线程告诉其他线程它们当前正在使用什么指针的方式)以及静态状态跟踪(等待每个线程达到不再使用任何指针的点)。最后一个在某些情况下非常高效。

在 Linux 内核中的很多数据结构是基于 RCU 的,并且有很多关于它们实现地方有意思的讨论和文章,这可以提供一个很棒的灵感。
在 Linux 内核中的很多数据结构是基于 RCU 的,并且有很多关于它们实现细节有意思的讨论和文章,这可以提供一个很棒的灵感。

进一步阅读:

Expand All @@ -52,7 +52,7 @@

(<a href="https://marabos.nl/atomics/inspiration.html#lock-free-linked-list" target="_blank">英文版本</a>)

扩展基本的 RCU 模式,你可以**增加**一个原子指针到结构体以指向下一个,以将其转换为*链表*。这允许线程以原子地方式**增加**或移除链表中的元素,而无需每次更新时复制整张表。
在基本的 RCU 模式上进行扩展,可以**增加**一个原子指针到结构体以指向下一个结构体,从而将其转换为*链表*。这允许线程以原子地方式**增加**或移除链表中的元素,而无需每次更新时复制整张表。

为了在表开始插入一个新元素,你仅需要分配该元素并将它的指针指向列表中的第一个元素,然后原子更新初始化指针以指向你最新分配到元素。

Expand All @@ -62,9 +62,9 @@

> 为了保持简单,你可以使用常规的 mutex 来避免并发的修改。这样,读仍然是一个无锁操作,但是你不需要担心处理并发修改。

从链表列表中分离元素后,你将遇到与之前相同的问题:它会等待,直到你释放它(或者以其他方式宣称所有权)。在这种情况下,我们讨论的基本的 RCU 模式的相同解决方案也有效
从链表列表中分离元素后,你将遇到与之前相同的问题:它会等待,直到你释放它(或者以其他方式宣称所有权)。在这种情况下,我们之前讨论的基本的 RCU 模式的相同解决方案在这里也有效

总的来说,你可以基于原子指针上的比较并交换操作,构建各种精心设计的无锁数据结构,但是你将总是需要一个好的策略来释放或者以其他方式收回分配的所有权。
总的来说,你可以基于原子指针上的「比较并交换」操作,构建各种精心设计的无锁数据结构,但是你将总是需要一个好的策略来释放或者以其他方式收回分配的所有权。

进一步阅读:

Expand All @@ -79,15 +79,15 @@

例如一个 mutex 可能作为单个 AtomicPtr 实现,其可以指向一个等待线程(列表)。

在这个列表中的每个元素都需要包含一些字段,这些字段用于唤醒相应的线程,例如 `std::thread::Thread` 对象。一些原子指针未使用的位可以用于存储 mutex 自身的状态,以及管理队列状态的任何所需的东西。
在这个列表中的每个元素都需要包含一些字段,这些字段用于唤醒相应的线程,例如 `std::thread::Thread` 对象。原子指针一些未使用的位可以用于存储 mutex 自身的状态,以及管理队列状态的任何所需的东西。

![ ](https://github.com/fwqaaq/Rust_Atomics_and_Locks/raw/main/picture/raal_10in04.png)

有很多可能的变体。队列可能由它自己的锁位保护,或者也可以实现为(部分地)无锁结构。元素不必在堆上分配,而可以是等待的线程的局部变量。队列可以是一个双向链表,不仅包含指向下一个元素的指针,同时也包含指向前一个元素。第一个元素也包含一个指向最后元素的指针,以便高效地在末尾追加一个元素
有很多可能的变体。队列可能由它自己的锁位保护,或者也可以实现为(部分地)无锁结构。元素不必在堆上分配,而可以是等待的线程的局部变量。队列可以是一个双向链表,不仅包含指向下一个元素的指针,同时也包含指向前一个元素。第一个元素也包含一个指向最后元素的指针,以便有效地在末尾追加一个元素

这种模式仅允许使用可以用于阻塞和唤醒单个线程的方式(例如 `parking`)来高效地锁定原语
这种模式仅允许使用可以用于阻塞和唤醒单个线程的方式(例如 `parking`)来实现高效的锁原语

Windows SRW 锁([第8章中的“一个轻巧的读写锁”](./8_Operating_System_Primitives.md#一个轻巧的读写锁))使用此模式实现。
Windows SRW 锁([第8章中的“精简的读写(SRW)锁”](./8_Operating_System_Primitives.md#精简的读写srw锁5))使用此模式实现。

进一步阅读:

Expand All @@ -98,13 +98,13 @@ Windows SRW 锁([第8章中的“一个轻巧的读写锁”](./8_Operating_Sy

(<a href="https://marabos.nl/atomics/inspiration.html#parking-lotbased-locks" target="_blank">英文版本</a>)

为了创建一个尽可能小的高效 mutex,你可以通过将队列移动到全局的数据结构,在 mutex 自身只留下 1 或者 2 个位,来构建基于队列的锁的想法。这样,mutex 仅需要是一个字节。你甚至可以把它放置在一些未使用的指针位中,这允许非常细粒度的锁定,几乎没有其他额外的开销。
为了创建一个尽可能小而高效的 mutex,你可以通过将队列移动到全局的数据结构,在 mutex 自身只留下 1 或者 2 个位,来构建基于队列锁的想法。这样,mutex 仅需要是一个字节。你甚至可以把它放置在一些未使用的指针位中,这允许非常细粒度的锁定,几乎没有其他额外的开销。

全局的数据结构可以是一个 `HashMap`,将内存地址映射到等待该地址的 mutex 的线程队列。全局的数据结构通常叫做 `parking lot`,因为它是一组被阻塞(`park`)的线程合集。

![ ](https://github.com/fwqaaq/Rust_Atomics_and_Locks/raw/main/picture/raal_10in05.png)

这种模式可以是泛化的,其不仅跟踪 mutex 的队列,同时也还跟踪条件变量和其他原语。通过跟踪任何原子变量的队列,这有效地提供了一种不在原生支持该功能的平台上实现类似 futex 功能的方式。
这种模式可以是广泛的,其不仅是跟踪 mutex 的队列,同时也还跟踪条件变量和其他原语。通过跟踪任何原子变量的队列,这有效地提供了一种不在原生支持该功能的平台上实现类似 futex 功能的方式。

这种模式最出名的是 2015 年在 WebKit 中的实现,在那里它被用来锁定 JavaScript 对象。它的实现启发了其他实现,例如流行的 parking_lot Rust crate。

Expand All @@ -113,7 +113,7 @@ Windows SRW 锁([第8章中的“一个轻巧的读写锁”](./8_Operating_Sy
* [WebKit 博客,“在 WebKit 中的锁定”](https://oreil.ly/6dPim)
* [parking_lot crate 的文档](https://oreil.ly/UPcXu)

## 顺序锁(SpinLock
## 顺序锁(Sequence Lock

(<a href="https://marabos.nl/atomics/inspiration.html#sequence-lock" target="_blank">英文版本</a>)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
* [无锁链表](./10_Ideas_and_Inspiration.md#无锁链表)
* [基于队列的锁](./10_Ideas_and_Inspiration.md#基于队列的锁)
* [基于阻塞的锁](./10_Ideas_and_Inspiration.md#基于阻塞的锁)
* [顺序锁](./10_Ideas_and_Inspiration.md#顺序锁spinlock)
* [顺序锁](./10_Ideas_and_Inspiration.md#顺序锁sequence-lock)
* [教学材料](./10_Ideas_and_Inspiration.md#教学材料)

注明:本文译自 <https://marabos.nl/atomics/>,若其它平台引用此翻译,也请注明出处。
Expand Down