Updated 'src/concurrency/shared-state'.

This commit is contained in:
Hector PENG
2026-04-07 11:55:15 +08:00
parent c8ebdb9dfb
commit b5d5e46fff
3 changed files with 119 additions and 91 deletions

View File

@@ -0,0 +1,6 @@
[package]
name = "shared-state"
version = "0.1.0"
edition = "2024"
[dependencies]

View File

@@ -0,0 +1,23 @@
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec! [];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println! ("结果:{}", *counter.lock().unwrap());
}

View File

@@ -1,37 +1,32 @@
# 状态共用的并发
# 共用状态的并发
**Shared-State Concurrency**
消息传递是处理并发的一种不错的方式,但并非唯一的方式。另一种方法是让多个线程访问同一共用的数据。请再考虑一下 Go 语言文档中口号的这一部分:“不要通过共用内存通信。”
通过共用内存的通信会是什么样子?此外,为何消息传递的拥护者会告诫不要使用共用内存?
在某种程度上,任何编程语言中的信道都类似于单一所有权,因为一旦咱们传输值到信道中,咱们就不应再使用该值。共用内存的并发就像多重所有权:多个线程可以同时访问同一内存位置。正如咱们在第 15 章中看到的虽然灵巧指针使多重所有权可行但多重所有权会增加复杂性因为这些不同的所有者需要管理。Rust 的类型系统和所有权规则,极大地助力了实现这种管理正确无虞。举个例子,我们来看看互斥量,这是共用内存的较为常见的并发原语之一。
消息传递是处理并发的一种很好方式,但其并非唯一的一种。另一种方式将是,多个线程访问同一共用数据。请重新考虑一下摘自 Go 语言文档的那句口号的这个部分“勿要经由共用内存通信。do not communicate by sharing memory.”
## 以互斥量控制访问
那么经由共用内存的通信,又会是怎样的呢?另外,为何消息传递方式拥趸们,会警告不要使用内存共用方式呢?
所谓 *互斥量mutex*,是 *mutual exclusion相互排斥* 的缩写,因为在互斥量下,在任何给定时间都只允许一个线程访问某一数据。要访问互斥量中的数据,线程就必须首先通过请求获取互斥量的锁来表明其访问意图。所谓 **,属于一种数据结构,是互斥量的一部分,跟踪当前谁有着对数据的独占访问权。因此,互斥量就被描述为通过锁定机制 *保护* 其包含的数据。
在某种程度上,任何编程语言中的信道,均类似于单一所有权,因为一旦咱们把值传递到信道,那么就不应再使用那个值了。内存共用的并发,则像是多重所有权:多个线程均可在同一时间,访问同一内存位置。正如咱们在第 15 章中曾见到过的那里的灵巧之中令到多重所有权可行多重所有权会因为这些不同所有者需要管理而增加复杂度。Rust 的类型系统与所有权规则极大地助力了实现这样的管理正确无误。作为一个示例接下来咱们就要看看作为共用内存的一种更常见并发原语即所谓的互斥量for an example, let's look at mutexes, one of the more common concurrency primitives for shared memory。
互斥量素来以难以使用著称,因为咱们必须记住以下两条规则:
1. 在使用数据之前,咱们必须尝试获取锁;
2. 在咱们使用完互斥量保护的数据后,必须解锁数据,以便其他线程可以获到锁。
## 运用互斥量实现一个时间仅允许一个线程访问数据
对于互斥量的现实世界比喻,请设想在仅有一只麦克风的会议上的小组讨论。在小组成员发言之前,他们必须请求或示意希望使用麦克风。再他们拿到麦克风后,他们可以随意发言,然后把麦克风交给下一位要求发言的小组成员。当某名小组成员用完麦克风后忘记交出麦克风时,其他人就不能发言。当这只共用麦克风的管理出现问题时,小组讨论将无法按计划进行!
**Using Mutexes to Allow Access to Data from One Thread at a Time**
*互斥mutex**相互排斥mutual exclusion* 的缩写,正如互斥量在任何给定时间,都只允许一个线程访问某个数据。要访问互斥量中的数据,线程就必须首先通过询问来获取到该互斥量的 *lock*表明其打算访问。所谓锁则是保持着当前是谁哪个线程有着对该数据排他性访问的追踪作为该互斥量一部分的一种数据结构the lock is a data structure that is part of the mutex that keeps track of who currently has exclusive access to the data。因此所谓互斥量就被描述为经由这种加锁系统*守护着guarding* 其所保存着的数据。
由于咱们务必要记住以下两条规则,互斥量便有了难以运用的名声:
- 在使用数据之前,咱们必须尝试获取到锁;
- 在完成互斥量所保护数据的操作时,咱们必须解开该数据,以便其他线程能够获取到锁。
至于互斥量的真实世界比喻,请设想在仅有一只麦克风的会议上的一个小组讨论。那么在小组成员能发言之前,他们就不得不请求或表明,他们打算使用麦克风。在他们得到麦克风时,他们便可以想要讲多长时间便讲多长时间,并在随后吧麦克风,递给下一位要求发言的小组成员。在某名小组成员于用完麦克风,却忘记交出麦克风时,就没有人能发言了。在这个共用麦克风的管理出错时,这个小组就将不会如计划那样运作了!
互斥量的管理非常棘手,难以做到正确无误,这正是许多人热衷于信道的原因。但是,归功于 Rust 的类型系统与所有权规则,咱们就无法在互斥量的加锁与解锁上出错了。
正确管理互斥量可能非常棘手,这也正是如何这么多人对信道如此推崇的原因。然而,得益于 Rust 的类型系统与所有权规则,咱们不会让加锁和解锁出错。
## `Mutex<T>` 的 API
下面是如何使用互斥量的一个示例,接下来咱们就要如下清单 16-12 中所给出的那样,通过在单一线程情形下使用互斥量开始:
作为使用互斥量的一个示例,我们来以在单线程的上下文中使用互斥量开始,如下清单 16-12 中所示。
<a name="listing_16-12"></a>
文件名:`src/main.rs`
```rust
@@ -45,28 +40,28 @@ fn main() {
*num = 6;
}
println! ("m = {:?}", m);
println! ("m = {m:?}");
}
```
*清单 16-12:为简化目在单线程情形下探讨 `Mutex<T>` 的 API*
**清单 16-12**:出于简化目在单线程上下文中探讨 `Mutex<T>` 的 API
与许多类型一样,们使用关联函数 `new` 创建出了一个 `Mutex<T>`。而为了访问这个互斥量内的数据,们使用 `lock` 方法获取锁。调用将阻塞当前线程,从而在轮到咱们拥有锁之前,当前线程就无法完成任何作。
与许多类型一样,们使用关联函数 `new` 创建 `Mutex<T>` 值。要访问互斥量内的数据,们使用 `lock` 方法获取锁。这一调用将阻塞当前线程,使其在轮到我们获得锁之前无法执行任何作。
若有另一持有锁的线程终止运行,那么到 `lock` 的调用会失败。在种情况下,没人能获得锁,因此咱们就选择了 `unwrap`,而在咱们陷入到那样的情形时,让这个线程终止运行。
持有锁的另一线程终止运行时,则对 `lock` 的调用会失败。在种情况下,没人能获得锁,因此我们选择在遇到这种情况时 `unwrap`,让这个线程终止运行。
获取锁后,咱们就可以对此示例中名为 `num` 的返回值,作为到互斥量内部数据的可变引用,而加以处理了。类型系统确保们在使用 `m` 的值前获取到锁。`m` 的类型为 `Mutex<i32>`,而 `i32`,因此咱们为了使用那个 `i32` 值, 就 *必须* 调用 `lock`。这是不能忘掉;否则类型系统不会让们访问那个内层的 `i32`
获取锁后,我们可以视返回值为到内部数据的可变引用,在这一情形下名为 `num`。类型系统确保们在使用 `m` 的值前获取到锁。`m` 的类型为 `Mutex<i32>`,而不是 `i32`,因此我们 *必须* 调用 `lock` 才能使用 `i32` 值。我们不能忘掉这点;否则类型系统不会让们访问内层的 `i32`
正如咱们可能怀疑的那样,`Mutex<T>` 是个灵巧指针。更准确地讲,到 `lock` 的调用,*返回的是* 封装在咱们曾以到 `unwrap` 调用处理的 `LockResult`,一个叫做 `MutexGuard` 的灵巧指针`MutexGuard` 灵巧之中实现了 `Deref`,来指向咱们的内层数据;这个灵巧指针还有着在 `MutexGuard` 超出作用域,即清单 16-12 的示例内存作用域结束处所发生时,自动释放锁的一个 `Drop` 实现。而其结果就是,由于锁的释放是自动发生的,因此咱们就不会面临忘记释放锁而阻塞互斥量其他线程使用的风险。
`lock` 的调用返回一个名为 `MutexGuard` 的类型,封装在我们通过调用 `unwrap` 处理的 `LockResult` 中。`MutexGuard` 实现了 `Deref` 特质,以指向内层数据;这一类型还有着 `Drop` 的实现,当 `MutexGuard` 超出作用域,即发生于内层作用域结束处,该实现会自动释放锁。因此,我们不会面临忘记释放锁而阻塞互斥量其他线程使用的风险,因为锁释放会自动发生
在弃用了该所之后,咱们就可以打印出该互斥量值,并看到咱们是能够把那个内层的 `i32`,修改`6`
在弃用锁后,我们可以打印互斥量值,并看到我们能够修改内层的 `i32` `6`
## 在多个线程间共用 `Mutex<T>`
## 共用 `Mutex<T>` 的访问
现在,我们来尝试使用 `Mutex<T>` 在多个线程间共用值。我们将启动 10 个线程,并让他们每个都递增计数器值加 1因此计数器会从 0 增加到 10。下面清单 16-13 中的示例将有个编译器报错,而我们将使用该报错进一步了解有关使用 `Mutex<T>` 的更多信息,以及 Rust 如何帮助我们正确使用他。
现在,咱们就来尝试使用 `Mutex<T>`,在多个线程见共用值。咱们将启动 10 个线程,并让他们分别都把一个计数器增加 `1`,因此那个计数器就会从 `0` 到达 `10`。接下来清单 16-13 中的示例,将有着一个编译器报错,同时咱们将使用那个报错,来掌握更多有关使用 `Mutex<T>`,以及 Rust 如何帮助咱们正确运用他的知识。
<a name="listing_16-13"></a>
文件名:`src/main.rs`
```rust
@@ -91,46 +86,55 @@ fn main() {
handle.join().unwrap();
}
println! ("结果{}", *counter.lock().unwrap());
println! ("结果:{}", *counter.lock().unwrap());
}
```
*清单 16-13:各自分别对由 `Mutex<T>` 所守护计数器递增的十个线程*
**清单 16-13**:十个线程,每个线程都递增一个`Mutex<T>` 保护的计数器
在清单 16-12 中一样,咱们创建出了一个在 `Mutex<T>` 内,保存着一个 `i32``counter` 变量。接下来,们通过对数字范围的迭代,创建出了 10 个线程。们使用 `thread::spawn`,并给全部线程同样闭包:把那个计数器迁移进到线程,通过调用 `lock` 方法取得那个 `Mutex<T>` 上的锁,并于随后加 `1`互斥量的值的这样一个闭包。在线程完成运行其闭包`num` 就会超出作用域而释放那把锁,从而另一线程便可以取该锁。
我们像在清单 16-12 中所做的那样,创建 `counter` 变量来保存 `Mutex<T>` `i32` 。接下来,们通过遍历一个数字范围来创建 10 个线程。们使用 `thread::spawn`,并给全部线程同样闭包:迁移计数器到线程,通过调用 `lock` 方法取得 `Mutex<T>` 上的锁,后加 `1` 到互斥量的值。在某一线程完成运行其闭包`num` 超出作用域而释放锁,以便另一线程可以取该锁。
在主线程中,们收集起了所有连接把手collect all the join handles。随后,如同在清单 16-2 中所做的那样,咱们在各个把手上调用 `join`确保所有现场运行完毕。在那个点位处,主线程将取得那把锁,并打印出该程序的结果。
在主线程中,们收集所有连接句柄。然后,如同我们在清单 16-2 中所做的那样,我们对每个句柄调用 `join`确保所有线程都完成。此时,主线程获取锁并打印这个程序的结果。
们曾暗示过此示例不会编译。现在来找出原因为何
们曾暗示这个示例不会编译。现在我们来找出原因!
```console
$ cargo run  ✔  
Compiling mutex_demo v0.1.0 (/home/peng/rust-lang/mutex_demo)
error[E0382]: use of moved value: `counter`
--> src/main.rs:12:36
$ cargo run
Compiling shared-state v0.1.0 (/home/hector/rust-lang-zh_CN/projects/shared-state)
error[E0382]: borrow of moved value: `counter`
--> src/main.rs:21:25
|
8 | let counter = Mutex::new(0);
| ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
5 | let counter = Mutex::new(0);
| ------- move occurs because `counter` has type `std::sync::Mutex<i32>`, which does not implement the `Copy` trait
...
12 | let handle = thread::spawn(move || {
| ^^^^^^^ value moved into closure here, in previous iteration of loop
13 | let mut num = counter.lock().unwrap();
| ------- use occurs due to use in closure
8 | for _ in 0..10 {
| -------------- inside of this loop
9 | let handle = thread::spawn(move || {
| ------- value moved into closure here, in previous iteration of loop
...
21 | println! ("结果:{}", *counter.lock().unwrap());
| ^^^^^^^ value borrowed here after move
|
help: consider moving the expression out of the loop so it is only moved once
|
8 ~ let mut value = counter.lock();
9 ~ for _ in 0..10 {
10 | let handle = thread::spawn(move || {
11 ~ let mut num = value.unwrap();
|
For more information about this error, try `rustc --explain E0382`.
error: could not compile `mutex_demo` due to previous error
error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
```
这个报错消息指出,其中的 `counter` 值在循环的上一次迭代中已被迁移。Rust 告诉们,不能将锁 `counter` 的所有权,迁移进到多个线程中。下面就来使用第 15 张中曾讨论的多重所有权方式,修正这个编译器报错。
报错消息指出,`counter` 这个值在循环的上一次迭代中已被迁移。Rust 是在告诉们,不能迁移 `counter`锁的所有权到多个线程中。我们来通过我们在第 15 章中讨论的多重所有权方法来修复这个编译器报错。
## 多线程下的多重所有权
**Multiple Ownership with Multiple Threads**
在第 15 章中,咱们曾通过使用灵巧指针 `Rc<T>`,来创建出一个引用计数的值,而将一个值赋予到多个所有者。下面就来完成那同样的操作,并看到会发生什么。咱们将在清单 16-14 中,把那个 `Mutex<T>` 封装在 `Rc<T>` 中,并在把所有权迁移到线程之前,克隆这个 `Rc<T>`
在第 15 章中,我们通过使用灵巧指针 `Rc<T>` 创建引用计数的值,而将值赋予多个所有者。我们来在这里执行同样的操作,看看会发生什么。我们将在下面清单 16-14 中,将 `Mutex<T>` 封装在 `Rc<T>` 中,并在迁移所有权到线程之前克隆该 `Rc<T>`
<a name="listing_16-14"></a>
文件名:`src/main.rs`
```rust
@@ -149,7 +153,6 @@ fn main() {
*num += 1;
});
handles.push(handle);
}
@@ -161,60 +164,59 @@ fn main() {
}
```
*清单 16-14尝试使用 `Rc<T>` 来实现多个线程拥有那个 `Mutex<T>`*
**清单 16-14**:尝试使用 `Rc<T>`允许多个线程拥有 `Mutex<T>`
又一次,咱们编译并得到......一些不同的报错!编译器给了咱们很多指教。
再次,我们得到并得到......不同的报错!编译器教会了我们很多东西:
```console
$ cargo run  ✔  
Compiling mutex_demo v0.1.0 (/home/peng/rust-lang/mutex_demo)
error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
--> src/main.rs:14:36
$ cargo run
Compiling shared-state v0.1.0 (/home/hector/rust-lang-zh_CN/projects/shared-state)
error[E0277]: `Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely
--> src/main.rs:11:36
|
14 | let handle = thread::spawn(move || {
11 | let handle = thread::spawn(move || {
| ------------- ^------
| | |
| ______________________|_____________within this `[closure@src/main.rs:14:36: 14:43]`
| ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}`
| | |
| | required by a bound introduced by this call
15 | | let mut num = counter.lock().unwrap();
16 | |
17 | | *num += 1;
18 | | });
| |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely
12 | | let mut num = counter.lock().unwrap();
13 | |
14 | | *num += 1;
15 | | });
| |_________^ `Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely
|
= help: within `[closure@src/main.rs:14:36: 14:43]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`
= help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc<std::sync::Mutex<i32>>`
note: required because it's used within this closure
--> src/main.rs:14:36
--> src/main.rs:11:36
|
14 | let handle = thread::spawn(move || {
11 | let handle = thread::spawn(move || {
| ^^^^^^^
note: required by a bound in `spawn`
--> /rustc/01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf/library/std/src/thread/functions.rs:125:1
For more information about this error, try `rustc --explain E0277`.
error: could not compile `mutex_demo` due to previous error
error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
```
喔,那报错消息真的非常罗嗦!而这些才是要关注的重要部分:`` `Rc<Mutex<i32>>` cannot be sent between threads safely ``。编译器还告诉咱们了其原因:`` the trait `Send` is not implemented for `Rc<Mutex<i32>>` ``。下一小节咱们就要讲到 `Send` 特质:他是确保咱们用到类型,是意图用于并发情形的特质之一。
哇,这条错误消息太啰嗦了!这才是要关注的重要部分:`` `Rc<Mutex<i32>>` cannot be sent between threads safely ``。编译器还告诉咱们原因:`` the trait `Send` is not implemented for `Rc<Mutex<i32>>` ``。我们将在下一小节讨论 `Send` 特质:他是确保我们在线程下使用的类型,适合在并发情形下使用的特质之一。
不幸的是,`Rc<T>` 跨线程共用上是不安全的。在 `Rc<T>` 管理引用计数时,他会增加每次 `clone` 调用的计数,并在每个克隆被弃用时减计数。但并未使用任何并发原语any concurrency primitives来确保那些对该计数的改变不被另一线程中断。这会导致错误的计数 -- 进而会导致内存泄漏,或在咱们未完成值处理之前,该值就已被启用这样的一些微妙代码缺陷。咱们所需要的,是像极了 `Rc<T>`,但会令到引用计数以线程安全方式得以改变的一种类型。
遗憾的是,`Rc<T>` 无法安全地跨线程共用。在 `Rc<T>` 管理引用计数时,他会增加每次 `clone` 调用的计数,并在每个克隆被弃用时减计数。但并未使用任何并发原语来确保对计数的更会不会被其他线程中断。这会导致错误的计数 -- 这些隐蔽的 bug 进而可能导致内存泄漏,或导致某个值在我们未使用完毕前被弃用。我们需要的是一种与 `Rc<T>` 完全相同,但能以线程安全方式修改引用计数的类型。
> **注**简单地说与各种编程语言中的那些原生数据类型primitive data types 一样所谓并发原语concurrency primitives指的就是用于并发编程的一些基本设施the basic facilities for concurrent programming这样的说法某种程度上是跨越某个语言家族比如 C 语言家族)。
> **注**简单地说与各种编程语言中的那些原生数据类型primitive data types 一样所谓并发原语concurrency primitives指的就是用于并发编程的一些基本设施the basic facilities for concurrent programming这样的说法某种程度上是跨越某个语言家族比如 C 语言家族)
>
> 参考:[What-are-concurrency-primitives-"K Symbol"](https://qr.ae/prtpz6)
## `Arc<T>` 下的原子引用计数
**Atomic Reference Counting with `Arc<T>`**
幸运的是,`Arc<T>` *正是* 一种类似于 `Rc<T>` 的类型,可以在并发情形下安全使用。其中 *a* 代表 *atomic*,表示他是一种 *原子引用计数* 类型。所谓原子,是另一种并发原语,我们不会在这里详细探讨:请参阅 [`std::sync::atomic` 的标准库文档](https://doc.rust-lang.org/std/sync/atomic/index.html) 了解更多细节。目前,咱们只需要知道原子类型的工作原理与基元类型相似,但可以安全地跨线程共用。
然后咱们可能想知道,为什么所有基元类型都不是原子的,以及为什么标准库默认实现为使用 `Arc<T>`。原因是线程安全会带来性能损失,而只有在咱们真的需要时才愿意付出。当咱们只是在单线程内对值执行操作时,若代码不必强制执行原子类型提供的保证,代码就会运行得更快。
幸运的是,`Arc<T>` *正是* 安全用于并发情形下的一个像是 `Rc<T>` 的类型。其中的 `a` 代表着 `原子atomic`,表示其是一种 *原子的引用计数atomically reference counted* 类型。原子类型是咱们不会在此详细讨论的一类额外并发原生类型:请参阅 [`std::sync::atomic` 的标准库文档](https://doc.rust-lang.org/std/sync/atomic/index.html),了解更多细节。此刻,咱们只需要知道这些原子类型会像那些原生类型一样运作,只不过他们对于跨线程的共用是安全的
到这里咱们可能想知道,为何全部原生类型不是原子的,以及为何标准库的那些类型,没有默认使用 `Arc<T>` 实现。原因就是线程安全自带了性能损失,而只有在咱们真的需要线程安全时,才会打算付出。在咱们只是在单线程里于一些值上执行操作时,若咱们的代码不必强制实现原子类型所提供的那些保证,那么这些代码就可以运行得快得多。
接下来回到那个示例:`Arc<T>` 与 `Rc<T>` 有着同样的 API因此通过修改其中的 `use` 语句行、到 `new` 的调用,以及到 `clone` 的调用,咱们就可以修复那个程序。清单 16-15 中的代码最终将会编译及运行:
我们来回到我们的示例:`Arc<T>` 与 `Rc<T>` 有着同样的 API因此我们通过修改 `use` 行、对 `new` 的调用,以及对 `clone` 的调用来修复程序。下面清单 16-15 中的代码最终将编译并运行
<a name="listing_16-15"></a>
文件名:`src/main.rs`
```rust
@@ -232,7 +234,6 @@ fn main() {
*num += 1;
});
handles.push(handle);
}
@@ -240,32 +241,30 @@ fn main() {
handle.join().unwrap();
}
println! ("结果{}", *counter.lock().unwrap());
println! ("结果:{}", *counter.lock().unwrap());
}
```
*清单 16-15为能够跨越多线程地共用所有权,而使用 `Arc<T>` 封装那个 `Mutex<T>`*
**清单 16-15**:使用 `Arc<T>` 封装 `Mutex<T>`,以便能够跨越多个线程共用所有权
代码将打印出下面的内容:
这段代码将打印以下内容:
```console
结果10
结果10
```
咱们就做到了!们从 `0` 计数到了 `10`,这或许看起来不是非常印象深刻,但他真的教会了们很多有关 `Mutex<T>` 线程安全的东西。咱们也可以用这个程序的架构,完成相比于增加计数器,一些更为复杂的操作。运用这策略,咱们可把某项计算,划分为一些独立部分,将这些部分拆解为多个线程,并于随后使用 `Mutex<T>` 来让各个各个线程,使用其自己部分对最终结果加以更新
我们做到了!们从 0 计数到了 10这看起来可能并不太令人印象深刻,但他确实教会了们很多有关 `Mutex<T>` 线程安全的知识。咱们也可以使用这个程序的架构,完成比单纯递增计数器更复杂的操作。运用这策略,咱们可以分解计算为独立部分,跨线程拆分这些部分,然后使用 `Mutex<T>` 让每个线程以其结果更新最终结果
请注意若咱们是在完成一些简单的数字运算,你们就有由 [标准库的 `std::sync::atomic` 模组](https://doc.rust-lang.org/std/sync/atomic/index.html) 提供的,相较于 `Mutex<T>` 更简单的一些类型。这些类型提供到原生类型安全、并发、原子的访问。咱们为这个示例而选择带有原生类型的 `Mutex<T>`目的是可以着重于 `Mutex<T>` 的工作原理。
请注意,当咱们正在进行简单的数字运算时,有一些 [标准库的 `std::sync::atomic` 模组](https://doc.rust-lang.org/std/sync/atomic/index.html) 提供的 `Mutex<T>` 更简单的类型。这些类型提供对原始类型安全、并发、原子的访问。对于这个示例我们选择与原生类型一起使用 `Mutex<T>`以便专注于 `Mutex<T>` 的工作原理。
### `RefCell<T>`/`Rc<T>` 与 `Mutex<T>`/`Arc<T>` 二者之间的相似点
### 比较 `RefCell<T>`/`Rc<T>` 与 `Mutex<T>`/`Arc<T>`
**Similarities Between `RefCell<T>`/`Rc<T>` and `Mutex<T>`/`Arc<T>`**
咱们可能已经注意到,`counter` 是不可变的,但我们可以获得到其内部值的可变引用;这意味着 `Mutex<T>` 提供了内部可变性,就像 `Cell` 家族一样。就像我们在第 15 章使用 `RefCell<T>` 来允许我们修改 `Rc<T>` 内的内容一样,我们使用 `Mutex<T>` 来修改 `Arc<T>` 内的内容。
咱们或许已经留意到,其中那个 `counter` 是不可变的,但咱们却能获取到其内部值的可变引用;这意味着与 `Cell` 家族the `Cell` family 所做的一样, `Mutex<T>` 提供了内部可变性。与咱们在第 15 章中使用 `RefCell<T>` 来实现修改 `Rc<T>` 内部内容同样的方式,咱们使用了 `Mutex<T>` 来修改 `Arc<T>` 内部内容
另一个要注意的细节是,当咱们使用 `Mutex<T>` 时Rust 无法保护咱们免受所有类别的逻辑错误的影响。回顾在第 15 章中使用 `Rc<T>` 会带来创建引用环的风险,其中两个 `Rc<T>` 值相互相引用,从而导致内存泄漏。同样,`Mutex<T>` 会带来创建 *死锁* 的风险。当某一操作需要锁住两个资源,且两个线程分别获取了其中一个锁,导致他们一直互相等待时,就会发生这种情况。若咱们对死锁感兴趣,请尝试创建一个有着死锁的 Rust 程序;然后,研究任意一门语言中针对互斥量的死锁缓解策略,并尝试在 Rust 中实现他们。`Mutex<T>` 和 `MutexGuard` 的标准库 API 文档提供了有用的信息
另一个需要注意的细节,便是在咱们使用 `Mutex<T>` 时Rust 无法保护咱们免于全部类别的逻辑错误。回顾在第 15 章中,`Rc<T>` 运用就伴随着创建出循环引用风险,其中两个 `Rc<T>` 值相互指向,导致内存泄漏。与此类似,`Mutex<T>` 则附带着创建出 *死锁deadlocks* 的风险。在某个操作需要锁住两项资源,同时两个线程分别均已请求获取两把锁中的一把时,就造成他们一直等待着对方释放各自所需的锁。若对死锁方面感兴趣,那么请尝试创建出有着死锁的一个 Rust 程序;随后就要研究任何一门语言中,互斥量的死锁消除策略,并试试在 Rust 中实现这些策略。`Mutex<T>` 和 `MutexGuard` 的标准库 API 文档,就提供了一些有用信息
咱们将通过讲解 `Send` 与 `Sync` 两个特质,以及怎样与一些定制类型来运用他们来完结本章。
我们将通过讨论 `Send` 与 `Sync` 两个特质,以及怎样与定制类型一起使用他们来结束本章
End