|
|
@ -11,24 +11,24 @@
|
|
|
|
>
|
|
|
|
>
|
|
|
|
> 不要共享内存来通讯;而是要通讯来共享内存。
|
|
|
|
> 不要共享内存来通讯;而是要通讯来共享内存。
|
|
|
|
|
|
|
|
|
|
|
|
那么“共享内存来通讯”看起来是怎样的呢?共享内存并发有点像多所有权:多个线程可以同时访问相同的内存位置。正如第十五章中智能指针使得多所有权成为可能时我们所看到的,这会增加额外的复杂性,因为需要以某种方式管理这些不同的所有者。
|
|
|
|
那么“共享内存来通讯”是怎样的呢?共享内存并发有点像多所有权:多个线程可以同时访问相同的内存位置。第十五章介绍了智能指针如何使得多所有权成为可能,然而这会增加额外的复杂性,因为需要以某种方式管理这些不同的所有者。
|
|
|
|
|
|
|
|
|
|
|
|
但是 Rust 的类型系统和所有权可以很好的帮助我们正确的进行管理。例如,让我们看看一个共享内存中更常见的并发原语:互斥器(mutexes)。
|
|
|
|
不过 Rust 的类型系统和所有权可以很好的帮助我们,正确的管理它们。以共享内存中更常见的并发原语:互斥器(mutexes)为例,让我们看看具体的情况。
|
|
|
|
|
|
|
|
|
|
|
|
### 互斥器一次只允许一个线程访问数据
|
|
|
|
### 互斥器一次只允许一个线程访问数据
|
|
|
|
|
|
|
|
|
|
|
|
**互斥器**(*mutex*)是一个用于共享内存的并发原语。它是“mutual exclusion”的缩写,也就是说,任何给定时间它只允许一个线程访问某些数据。互斥器以难以使用著称,因为你不得不记住:
|
|
|
|
**互斥器**(*mutex*)是一种用于共享内存的并发原语。它是“mutual exclusion”的缩写,也就是说,任意时间,它只允许一个线程访问某些数据。互斥器以难以使用著称,因为你不得不记住:
|
|
|
|
|
|
|
|
|
|
|
|
1. 必须记住在使用数据之前尝试获取锁。
|
|
|
|
1. 在使用数据之前尝试获取锁。
|
|
|
|
2. 一旦处理完被互斥器所保护的数据之后,必须记得解锁数据这样其他线程才能够获取锁。
|
|
|
|
2. 处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。
|
|
|
|
|
|
|
|
|
|
|
|
对于一个现实中的互斥器的例子,想象一下在一个会议中的专门小组讨论会上,不过只有一个麦克风。在一个小组成员可能发言之前,他们必须请求或示意他们需要使用麦克风。一旦得到了麦克风,他们可以发言任意长的时间,接着将麦克风交给下一个希望讲话的小组成员。如果小组成员在没有麦克风的时候就开始叫喊或者在其他成员发言结束之前就取得麦克风将是很无理的。如果对这个共享的麦克风的管理因为任何这些原因而出现问题,讨论会将无法如期进行。
|
|
|
|
现实中也有互斥器的例子,想象一下在一个会议中,只有一个麦克风。如果一个成员要发言,他必须请求使用麦克风。一旦得到了麦克风,他可以畅所欲言,然后将麦克风交给下一个希望讲话的成员。如果成员在没有麦克风的时候就开始叫喊,或者在其他成员发言结束之前就拿走麦克风,是很不合适的。如果这个共享的麦克风因为此类原因而出现问题,会议将无法正常进行。
|
|
|
|
|
|
|
|
|
|
|
|
正确的管理互斥器是异常复杂的,这也就是为什么这么多人都热衷于通道。然而,在 Rust 中,得益于类型系统和所有权,我们不可能会在锁和解锁上出错。
|
|
|
|
正确的管理互斥器异常复杂,这也是许多人之所以热衷于通道的原因。然而,在 Rust 中,得益于类型系统和所有权,我们不会在锁和解锁上出错。
|
|
|
|
|
|
|
|
|
|
|
|
### `Mutex<T>`的 API
|
|
|
|
### `Mutex<T>`的 API
|
|
|
|
|
|
|
|
|
|
|
|
让我们看看列表 16-12 中使用互斥器的例子,现在并不涉及到多线程:
|
|
|
|
让我们看看列表 16-12 中使用互斥器的例子,现在不涉及多线程:
|
|
|
|
|
|
|
|
|
|
|
|
<span class="filename">Filename: src/main.rs</span>
|
|
|
|
<span class="filename">Filename: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
|
@ -50,9 +50,9 @@ fn main() {
|
|
|
|
<span class="caption">Listing 16-12: Exploring the API of `Mutex<T>` in a
|
|
|
|
<span class="caption">Listing 16-12: Exploring the API of `Mutex<T>` in a
|
|
|
|
single threaded context for simplicity</span>
|
|
|
|
single threaded context for simplicity</span>
|
|
|
|
|
|
|
|
|
|
|
|
与很多类型一样,我们通过叫做`new`的关联函数来创建一个`Mutex<T>`。为了访问互斥器中的数据,使用`lock`方法来获取锁。这个调用会阻塞到直到轮到我们拥有锁为止。如果另一个线程拥有锁接着那个线程 panic 了则这个调用会失败。类似于上一部分列表 16-6 那样,我们暂时使用`unwrap()`,至于更好的错误处理,请查看第九章中提供的更好的工具。
|
|
|
|
像很多类型一样,我们使用关联函数 `new` 来创建一个 `Mutex<T>`。使用`lock`方法获取锁,以访问互斥器中的数据。这个调用会阻塞,直到我们拥有锁为止。如果另一个线程拥有锁,并且那个线程 panic 了,则这个调用会失败。类似于列表 16-6 那样,我们暂时使用 `unwrap()` 进行错误处理,或者使用第九章中提及的更好的工具。
|
|
|
|
|
|
|
|
|
|
|
|
一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用使用了。类型系统是 Rust 如何保证使用值之前必须获取锁的:`Mutex<i32>`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的;因为类型系统是不会允许的。
|
|
|
|
一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用来使用了。类型系统是 Rust 如何保证使用值之前必须获取锁的:`Mutex<i32>`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的;因为类型系统是不会允许的。
|
|
|
|
|
|
|
|
|
|
|
|
与你可能怀疑的一样,`Mutex<T>`是一个智能指针。好吧,更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。
|
|
|
|
与你可能怀疑的一样,`Mutex<T>`是一个智能指针。好吧,更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。
|
|
|
|
|
|
|
|
|
|
|
|