pull/573/head
sunface 3 years ago
commit 50df514da8

@ -10,8 +10,8 @@
</div> </div>
- 在线阅读 - 在线阅读
- 官方 [https://course.rs](https://course.rs) - Rust编程学院 [https://course.rs](https://course.rs)
- 知乎: [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017) - 知乎专栏 [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017)
- 配套项目 - 配套项目
- [Rust语言实战](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D - [Rust语言实战](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D
@ -64,6 +64,7 @@ QQ群 1009730433 欢迎大家加入,一起 happy一起进步。
- [@1132719438](https://github.com/1132719438) - [@1132719438](https://github.com/1132719438)
- [@Mintnoii](https://github.com/Mintnoii) - [@Mintnoii](https://github.com/Mintnoii)
- [@Rustln](https://github.com/rustln) - [@Rustln](https://github.com/rustln)
- [@zongzi531](https://github.com/zongzi531)
尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~ 尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~

@ -62,6 +62,24 @@ fn main() {
正因为智能指针的使用,使得我们无需任何操作就能获取其中的数据。 如果释放锁,你需要做的仅仅是做好锁的作用域管理,例如上述代码的内部花括号使用,建议读者尝试下去掉内部的花括号,然后再次尝试获取第二个锁`num1`,看看会发生什么,友情提示:不会报错,但是主线程会永远阻塞,因为不幸发生了死锁。 正因为智能指针的使用,使得我们无需任何操作就能获取其中的数据。 如果释放锁,你需要做的仅仅是做好锁的作用域管理,例如上述代码的内部花括号使用,建议读者尝试下去掉内部的花括号,然后再次尝试获取第二个锁`num1`,看看会发生什么,友情提示:不会报错,但是主线程会永远阻塞,因为不幸发生了死锁。
```rust
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
let mut num = m.lock().unwrap();
*num = 6;
// 锁还没有被 drop 就尝试申请下一个锁,导致主线程阻塞
// drop(num); // 手动 drop num ,可以让 num1 申请到下个锁
let mut num1 = m.lock().unwrap();
*num1 = 7;
// drop(num1); // 手动 drop num1 ,观察打印结果的不同
println!("m = {:?}", m);
}
```
#### 多线程中使用 Mutex #### 多线程中使用 Mutex
单线程中使用锁,说实话纯粹是为了演示功能,毕竟多线程才是锁的舞台。 现在,我们再来看看,如何在多线程下使用`Mutex`来访问同一个资源. 单线程中使用锁,说实话纯粹是为了演示功能,毕竟多线程才是锁的舞台。 现在,我们再来看看,如何在多线程下使用`Mutex`来访问同一个资源.
@ -114,14 +132,14 @@ error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
// `Rc`无法在线程中安全的传输 // `Rc`无法在线程中安全的传输
--> src/main.rs:11:22 --> src/main.rs:11:22
| |
11 | let handle = thread::spawn(move || { 13 | let handle = thread::spawn(move || {
| ______________________^^^^^^^^^^^^^_- | ______________________^^^^^^^^^^^^^_-
| | | | | |
| | `Rc<Mutex<i32>>` cannot be sent between threads safely | | `Rc<Mutex<i32>>` cannot be sent between threads safely
12 | | let mut num = counter.lock().unwrap(); 14 | | let mut num = counter.lock().unwrap();
13 | | 15 | |
14 | | *num += 1; 16 | | *num += 1;
15 | | }); 17 | | });
| |_________- within this `[closure@src/main.rs:11:36: 15:10]` | |_________- within this `[closure@src/main.rs:11:36: 15:10]`
| |
= help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>` = help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`
@ -231,22 +249,22 @@ fn main() {
for _ in 0..1 { for _ in 0..1 {
// 线程1 // 线程1
if i_thread % 2 == 0 { if i_thread % 2 == 0 {
// 锁住mutex1 // 锁住MUTEX1
let guard: MutexGuard<i64> = MUTEX1.lock().unwrap(); let guard: MutexGuard<i64> = MUTEX1.lock().unwrap();
println!("线程 {} 锁住了mutex1接着准备去锁mutex2 !", i_thread); println!("线程 {} 锁住了MUTEX1接着准备去锁MUTEX2 !", i_thread);
// 当前线程睡眠一小会儿等待线程2锁住mutex2 // 当前线程睡眠一小会儿等待线程2锁住MUTEX2
sleep(Duration::from_millis(10)); sleep(Duration::from_millis(10));
// 去锁mutex2 // 去锁MUTEX2
let guard = MUTEX2.lock().unwrap(); let guard = MUTEX2.lock().unwrap();
// 线程2 // 线程2
} else { } else {
// 锁住mutex2 // 锁住MUTEX2
let _guard = MUTEX2.lock().unwrap(); let _guard = MUTEX2.lock().unwrap();
println!("线程 {} 锁住了mutex2, 准备去锁mutex1", i_thread); println!("线程 {} 锁住了MUTEX2, 准备去锁MUTEX1", i_thread);
let _guard = MUTEX1.lock().unwrap(); let _guard = MUTEX1.lock().unwrap();
} }
@ -265,9 +283,9 @@ fn main() {
在上面的描述中,我们用了"可能"二字,原因在于死锁在这段代码中不是必然发生的,总有一次运行你能看到最后一行打印输出。这是由于子线程的初始化顺序和执行速度并不确定,我们无法确定哪个线程中的锁先被执行,因此也无法确定两个线程对锁的具体使用顺序。 在上面的描述中,我们用了"可能"二字,原因在于死锁在这段代码中不是必然发生的,总有一次运行你能看到最后一行打印输出。这是由于子线程的初始化顺序和执行速度并不确定,我们无法确定哪个线程中的锁先被执行,因此也无法确定两个线程对锁的具体使用顺序。
但是,可以简单的说明下死锁发生的必然条件:线程 1 锁住了`mutex1`并且线程`2`锁住了`mutex2`,然后线程 1 试图去访问`mutex2`,同时线程`2`试图去访问`mutex1`,就会死锁。 因为线程 2 需要等待线程 1 释放`mutex1`后,才会释放`mutex2`,而与此同时,线程 1 需要等待线程 2 释放`mutex2`后才能释放`mutex1`,这种情况造成了两个线程都无法释放对方需要的锁,最终死锁。 但是,可以简单的说明下死锁发生的必然条件:线程 1 锁住了`MUTEX1`并且线程`2`锁住了`MUTEX2`,然后线程 1 试图去访问`MUTEX2`,同时线程`2`试图去访问`MUTEX1`,就会死锁。 因为线程 2 需要等待线程 1 释放`MUTEX1`后,才会释放`MUTEX2`,而与此同时,线程 1 需要等待线程 2 释放`MUTEX2`后才能释放`MUTEX1`,这种情况造成了两个线程都无法释放对方需要的锁,最终死锁。
那么为何某些时候,死锁不会发生?原因很简单,线程 2 在线程 1 锁`mutex1`之前,就已经全部执行完了,随之线程 2 的`mutex2`和`mutex1`被全部释放,线程 1 对锁的获取将不再有竞争者。 同理,线程 1 若全部被执行完,那线程 2 也不会被锁,因此我们在线程 1 中间加一个睡眠,增加死锁发生的概率。如果你在线程 2 中同样的位置也增加一个睡眠,那死锁将必然发生! 那么为何某些时候,死锁不会发生?原因很简单,线程 2 在线程 1 锁`MUTEX1`之前,就已经全部执行完了,随之线程 2 的`MUTEX2`和`MUTEX1`被全部释放,线程 1 对锁的获取将不再有竞争者。 同理,线程 1 若全部被执行完,那线程 2 也不会被锁,因此我们在线程 1 中间加一个睡眠,增加死锁发生的概率。如果你在线程 2 中同样的位置也增加一个睡眠,那死锁将必然发生!
#### try_lock #### try_lock
@ -292,26 +310,26 @@ fn main() {
for _ in 0..1 { for _ in 0..1 {
// 线程1 // 线程1
if i_thread % 2 == 0 { if i_thread % 2 == 0 {
// 锁住mutex1 // 锁住MUTEX1
let guard: MutexGuard<i64> = MUTEX1.lock().unwrap(); let guard: MutexGuard<i64> = MUTEX1.lock().unwrap();
println!("线程 {} 锁住了mutex1接着准备去锁mutex2 !", i_thread); println!("线程 {} 锁住了MUTEX1接着准备去锁MUTEX2 !", i_thread);
// 当前线程睡眠一小会儿等待线程2锁住mutex2 // 当前线程睡眠一小会儿等待线程2锁住MUTEX2
sleep(Duration::from_millis(10)); sleep(Duration::from_millis(10));
// 去锁mutex2 // 去锁MUTEX2
let guard = MUTEX2.try_lock(); let guard = MUTEX2.try_lock();
println!("线程1获取mutex2锁的结果: {:?}",guard); println!("线程1获取MUTEX2锁的结果: {:?}",guard);
// 线程2 // 线程2
} else { } else {
// 锁住mutex2 // 锁住MUTEX2
let _guard = MUTEX2.lock().unwrap(); let _guard = MUTEX2.lock().unwrap();
println!("线程 {} 锁住了mutex2, 准备去锁mutex1", i_thread); println!("线程 {} 锁住了MUTEX2, 准备去锁MUTEX1", i_thread);
sleep(Duration::from_millis(10)); sleep(Duration::from_millis(10));
let guard = MUTEX1.try_lock(); let guard = MUTEX1.try_lock();
println!("线程2获取mutex1锁的结果: {:?}",guard); println!("线程2获取MUTEX1锁的结果: {:?}",guard);
} }
} }
})); }));
@ -329,10 +347,10 @@ fn main() {
为了演示`try_lock`的作用,我们特定使用了之前必定会死锁的代码,并且将`lock`替换成`try_lock`,与之前的结果不同,这段代码将不会再有死锁发生: 为了演示`try_lock`的作用,我们特定使用了之前必定会死锁的代码,并且将`lock`替换成`try_lock`,与之前的结果不同,这段代码将不会再有死锁发生:
```console ```console
线程 0 锁住了mutex1接着准备去锁mutex2 ! 线程 0 锁住了MUTEX1接着准备去锁MUTEX2 !
线程 1 锁住了mutex2, 准备去锁mutex1 线程 1 锁住了MUTEX2, 准备去锁MUTEX1
线程2获取mutex1锁的结果: Err("WouldBlock") 线程2获取MUTEX1锁的结果: Err("WouldBlock")
线程1获取mutex2锁的结果: Ok(0) 线程1获取MUTEX2锁的结果: Ok(0)
死锁没有发生 死锁没有发生
``` ```

Loading…
Cancel
Save