Skip to content

Commit 01e315e

Browse files
author
wayslog
committed
pdf/gitbook/mobi file format error
1 parent 58515a7 commit 01e315e

9 files changed

Lines changed: 38 additions & 23 deletions

File tree

23-concurrency-parallel-threads/24-02-message-passing.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ note: `alloc::rc::Rc<Student>` cannot be sent between threads safely
9090

9191
虽然有这些非`Send`的情况,但是逃不过编译器的火眼金睛,只要你错误地使用了消息类型,编译器都会给出类似于上面的错误提示。我们要担心的不是这些,因为错误更容易出现在新创建的自定义类,有下面两点需要注意:
9292

93-
1. 如果自定义类的所有字段都是`Send`,那么这个自定义类也是`Send`,反之,如果有一个字段是`!Send`,那么这个自定义类也是`!Send`。如果类的字段存在递归包含的情况,按照该原则以此类推来推论类是`Send`还是`!Send`
93+
1. 如果自定义类的所有字段都是`Send`,那么这个自定义类也是`Send`
94+
反之,如果有一个字段是`!Send`,那么这个自定义类也是`!Send`
95+
如果类的字段存在递归包含的情况,按照该原则以此类推来推论类是`Send`还是`!Send`
96+
9497
2. 在为一个自定义类实现`Send`或者`!Send`时,必须确保符合它的约定。
9598

9699
到此,消息类型的相关知识已经介绍完了,说了这么久,也该让大家自己练习一下了:请实现一个自定义类,该类包含一个Rc字段,让这个类变成可以在通道中发送的消息类型。
@@ -139,9 +142,13 @@ receive 2
139142

140143
在代码中,我们故意让`main`所在的主线程睡眠2秒,从而让发送者所在线程优先执行,通过结果可以发现,发送者发送消息时确实没有阻塞。还记得在前面提到过很多关于通道的问题吗?从这个例子里面还发现什么没?除了不阻塞之外,我们还能发现另外的三个特征:
141144

142-
1. 通道是可以同时支持多个发送者的,通过`clone`的方式来实现,这类似于`Rc`的共享机制,其实从`Channel`所在的库名`std::sync::mpsc`也可以知道这点,因为`mpsc`就是多生产者单消费者(multi)的简写,可以有多个发送者,但只能有一个接收者,即支持的N:1模式。
145+
1. 通道是可以同时支持多个发送者的,通过`clone`的方式来实现。
146+
这类似于`Rc`的共享机制,其实从`Channel`所在的库名`std::sync::mpsc`也可以知道这点,因为`mpsc`就是多生产者单消费者(multi)的简写。
147+
可以有多个发送者,但只能有一个接收者,即支持的N:1模式。
148+
149+
2. 异步通道具备消息缓存的功能,因为1和2是在没有接收之前就发了的,在此之后还能接收到这两个消息。
143150

144-
2. 异步通道具备消息缓存的功能,因为1和2是在没有接收之前就发了的,在此之后还能接收到这两个消息。那么通道到底能缓存多少消息?在理论上是无穷的,尝试一下便知:
151+
那么通道到底能缓存多少消息?在理论上是无穷的,尝试一下便知:
145152

146153
```rust
147154
use std::sync::mpsc;
@@ -225,4 +232,4 @@ after send
225232

226233
对照上面两点和运行结果来分析,由于主线程在接收消息前先睡眠了,从而子线程这个时候会被调度执行发送消息,由于通道能缓存的消息为0,而这个时候接收者还没有接收,所以`tx.send(1).unwrap()`就会阻塞子线程,直到主线程接收消息,即执行`println!("receive {}", rx.recv().unwrap());`。运行结果印证了这点,要是没阻塞,那么在`before send`之后就应该是`after send`了。
227234

228-
相比较而言,异步通道更没有责任感一些,因为消息发送者一股脑的只管发送,不管接收者是否能快速处理。这样就可能出现通道里面缓存大量的消息得不到处理,从而占用大量的内存,最终导致内存耗尽。而同步通道则能避免这种问题,把接受者的压力能传递到发送者,从而一直传递下去。
235+
相比较而言,异步通道更没有责任感一些,因为消息发送者一股脑的只管发送,不管接收者是否能快速处理。这样就可能出现通道里面缓存大量的消息得不到处理,从而占用大量的内存,最终导致内存耗尽。而同步通道则能避免这种问题,把接受者的压力能传递到发送者,从而一直传递下去。

23-concurrency-parallel-threads/24-04-synchronize.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
### 等待
99
Rust中线程等待和其他语言在机制上并无差异,大致有下面几种:
1010

11-
1. 等待一段时间后,再接着继续执行。看起来就像一个人工作累了,休息一会再工作。通过调用相关的API可以让当前线程暂停执行进入睡眠状态,此时调度器不会调度它执行,等过一段时间后,线程自动进入就绪状态,可以被调度执行,继续从之前睡眠时的地方执行。对应的API有`std::thread::sleep`,`std::thread::sleep_ms`,`std::thread::park_timeout`,`std::thread::park_timeout_ms`,还有一些类似的其他API,由于太多,详细信息就请参见官网[`std::thread`](https://doc.rust-lang.org/stable/std/thread/index.html)。
12-
2. 这一种方式有点特殊,时间非常短,就一个时间片,当前线程自己主动放弃当前时间片的调度,让调度器重新选择线程来执行,这样就把运行机会给了别的线程,但是要注意的是,如果别的线程没有更好的理由执行,当然最后执行机会还是它的。在实际的应用业务中,比如生产者制造出一个产品后,可以放弃一个时间片,让消费者获得执行机会,从而快速地消费才生产的产品。这样的控制粒度非常小,需要合理使用,如果需要连续放弃多个时间片,可以借用循环实现。对应的API是`std::thread::yield_now`,详细信息参见官网[`std::thread`](https://doc.rust-lang.org/stable/std/thread/index.html)。
13-
3. 1和2的等待都无须其他线程的协助,即可在一段时间后继续执行。最后我们还遇到一种等待,是需要其他线程参与,才能把等待的线程叫醒,否则,线程会一直等待下去。好比一个女人,要是没有遇到一个男人,就永远不可能摆脱单身的状态。相关的API包括`std::thread::JoinHandle::join`,`std::thread::park`,`std::sync::Mutex::lock`等,还有一些同步相关的类的API也会导致线程等待。详细信息参见官网[`std::thread`](https://doc.rust-lang.org/stable/std/thread/index.html)和[`std::sync`](https://doc.rust-lang.org/stable/std/sync/index.html)。
11+
* 等待一段时间后,再接着继续执行。看起来就像一个人工作累了,休息一会再工作。通过调用相关的API可以让当前线程暂停执行进入睡眠状态,此时调度器不会调度它执行,等过一段时间后,线程自动进入就绪状态,可以被调度执行,继续从之前睡眠时的地方执行。对应的API有`std::thread::sleep``std::thread::sleep_ms``std::thread::park_timeout``std::thread::park_timeout_ms`,还有一些类似的其他API,由于太多,详细信息就请参见官网[`std::thread`](https://doc.rust-lang.org/stable/std/thread/index.html)
12+
* 这一种方式有点特殊,时间非常短,就一个时间片,当前线程自己主动放弃当前时间片的调度,让调度器重新选择线程来执行,这样就把运行机会给了别的线程,但是要注意的是,如果别的线程没有更好的理由执行,当然最后执行机会还是它的。在实际的应用业务中,比如生产者制造出一个产品后,可以放弃一个时间片,让消费者获得执行机会,从而快速地消费才生产的产品。这样的控制粒度非常小,需要合理使用,如果需要连续放弃多个时间片,可以借用循环实现。对应的API是`std::thread::yield_now`,详细信息参见官网[`std::thread`](https://doc.rust-lang.org/stable/std/thread/index.html)
13+
* 1和2的等待都无须其他线程的协助,即可在一段时间后继续执行。最后我们还遇到一种等待,是需要其他线程参与,才能把等待的线程叫醒,否则,线程会一直等待下去。好比一个女人,要是没有遇到一个男人,就永远不可能摆脱单身的状态。相关的API包括`std::thread::JoinHandle::join``std::thread::park``std::sync::Mutex::lock`等,还有一些同步相关的类的API也会导致线程等待。详细信息参见官网[`std::thread`](https://doc.rust-lang.org/stable/std/thread/index.html)[`std::sync`](https://doc.rust-lang.org/stable/std/sync/index.html)
1414

1515
第一种和第三种等待方式,其实我们在上面的介绍中,都已经遇到过了,它们也是使用的最多的两种方式。在此,也可以回过头去看看前面的使用方式和使用效果,结合自己的理解,做一些简单的练习。
1616

@@ -19,11 +19,11 @@ Rust中线程等待和其他语言在机制上并无差异,大致有下面几
1919
### 通知
2020
看是简单的通知,在编程时也需要注意以下几点:
2121

22-
1. 通知必然是因为有等待,所以通知和等待几乎都是成对出现的,比如`std::sync::Condvar::wait`和`std::sync::Condvar::notify_one`,`std::sync::Condvar::notify_all`。
23-
2. 等待所使用的对象,与通知使用的对象是同一个对象,从而该对象需要在多个线程之间共享,参见下面的例子。
24-
3. 除了`Condvar`之外,其实*锁*也是具有自动通知功能的,当持有锁的线程释放锁的时候,等待锁的线程就会自动被唤醒,以抢占锁。关于锁的介绍,在下面有详解。
25-
4. 通过条件变量和锁,还可以构建更加复杂的自动通知方式,比如`std::sync::Barrier`。
26-
5. 通知也可以是1:1的,也可以是1:N的,`Condvar`可以控制通知一个还是N个,而锁则不能控制,只要释放锁,所有等待锁的其他线程都会同时醒来,而不是只有最先等待的线程。
22+
* 通知必然是因为有等待,所以通知和等待几乎都是成对出现的,比如`std::sync::Condvar::wait``std::sync::Condvar::notify_one``std::sync::Condvar::notify_all`
23+
* 等待所使用的对象,与通知使用的对象是同一个对象,从而该对象需要在多个线程之间共享,参见下面的例子。
24+
* 除了`Condvar`之外,其实**也是具有自动通知功能的,当持有锁的线程释放锁的时候,等待锁的线程就会自动被唤醒,以抢占锁。关于锁的介绍,在下面有详解。
25+
* 通过条件变量和锁,还可以构建更加复杂的自动通知方式,比如`std::sync::Barrier`
26+
* 通知也可以是1:1的,也可以是1:N的,`Condvar`可以控制通知一个还是N个,而锁则不能控制,只要释放锁,所有等待锁的其他线程都会同时醒来,而不是只有最先等待的线程。
2727

2828
下面我们分析一个简单的例子:
2929

@@ -63,10 +63,10 @@ after wait
6363
```
6464
这个例子展示了如何通过条件变量和锁来控制新建线程和主线程的同步,让主线程等待新建线程执行后,才能继续执行。从结果来看,功能上是实现了。对于上面这个例子,还有下面几点需要说明:
6565

66-
1. `Mutex`是Rust中的一种锁。
67-
2. `Condvar`需要和`Mutex`一同使用,因为有`Mutex`保护,`Condvar`并发才是安全的。
68-
3. `Mutex::lock`方法返回的是一个`MutexGuard`,在离开作用域的时候,自动销毁,从而自动释放锁,从而避免锁没有释放的问题。
69-
4. `Condvar`在等待时,时会释放锁的,被通知唤醒时,会重新获得锁,从而保证并发安全。
66+
* `Mutex`是Rust中的一种锁。
67+
* `Condvar`需要和`Mutex`一同使用,因为有`Mutex`保护,`Condvar`并发才是安全的。
68+
* `Mutex::lock`方法返回的是一个`MutexGuard`,在离开作用域的时候,自动销毁,从而自动释放锁,从而避免锁没有释放的问题。
69+
* `Condvar`在等待时,时会释放锁的,被通知唤醒时,会重新获得锁,从而保证并发安全。
7070

7171
到此,你应该对锁比较感兴趣了,为什么需要锁?锁存在的目的就是为了保证资源在同一个时间,能有序地被访问,而不会出现异常数据。但其实要做到这一点,也并不是只有锁,包括锁在内,主要涉及两种基本方式:
7272

@@ -166,4 +166,4 @@ fn main() {
166166

167167
在Rust中,`Mutex`是一种独占锁,同一时间只有一个线程能持有这个锁。这种锁会导致所有线程串行起来,这样虽然保证了安全,但效率并不高。对于写少读多的情况来说,如果在没有写的情况下,都是读取,那么应该是可以并发执行的,为了达到这个目的,几乎所有的编程语言都提供了一种叫读写锁的机制,Rust中也存在,叫[`std::sync::RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html),在使用上同`Mutex`差不多,在此就留给大家自行练习了。
168168

169-
同步是多线程编程的永恒主题,Rust已经为我们提供了良好的编程范式,并强加检查,即使你之前没有怎么接触过,用Rust也能编写出非常安全的多线程程序。
169+
同步是多线程编程的永恒主题,Rust已经为我们提供了良好的编程范式,并强加检查,即使你之前没有怎么接触过,用Rust也能编写出非常安全的多线程程序。

23-concurrency-parallel-threads/24-05-parallel.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ extern crate rayon;
1212
use rayon::prelude::*;
1313

1414
fn main() {
15-
let mut colors = [-20.0f32, 0.0, 20.0, 40.0, 80.0, 100.0, 150.0, 180.0, 200.0, 250.0, 300.0];
15+
let mut colors = [-20.0f32, 0.0, 20.0, 40.0,
16+
80.0, 100.0, 150.0, 180.0, 200.0, 250.0, 300.0];
1617
println!("original: {:?}", &colors);
1718

1819
colors.par_iter_mut().for_each(|color| {
19-
let c : f32 = if *color < 0.0 { 0.0 } else if *color > 255.0 { 255.0 } else { *color };
20+
let c : f32 = if *color < 0.0 {
21+
0.0
22+
} else if *color > 255.0 {
23+
255.0 } else { *color
24+
};
2025
*color = c / 255.0;
2126
});
2227
println!("transformed: {:?}", &colors);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
wayslog@eins.3841:1459482450

34-std/34-01-process.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,5 @@ fn main() {
120120
这段代码相当于给了stdout一个缓冲区,这个缓冲区直到我们计算完成之后才被读取,因此就不会造成乱序输出的问题了。
121121

122122
这边需要注意的一点是,一旦你开启了一个子进程,那么,无论你程序是怎么处理的,最后一定要记得对这个child调用wait或者wait_with_output,除非你显式的调用kill。因为如果父进程不wait它的话,它将会变成一个僵尸进程!!!
123-
注: 以上问题为Linux下Python多进程的日常问题,见怪不怪也不奇怪了。
123+
124+
**: 以上问题为Linux下Python多进程的日常问题,见怪不怪也不奇怪了。

34-std/34-03-net.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ fn server<A: ToSocketAddrs>(addr: &A) -> io::Result<()> {
1616
Ok(mut st) => {
1717
// 我们总是要求client端先发送数据
1818
// 准备一个超大的缓冲区
19-
// 当然了,在实际的生活中我们一般会采用环形缓冲来重复利用内存。这里仅作演示,是一种很低效的做法
19+
// 当然了,在实际的生活中我们一般会采用环形缓冲来重复利用内存。
20+
// 这里仅作演示,是一种很低效的做法
2021
let mut buf: Vec<u8> = vec![0u8; 1024];
2122
// 通过try!方法来解包
2223
// try!方法的重点是需要有特定的Error类型与之配合

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ The Rust primer for beginners.
133133

134134
## 版权规定
135135

136-
本书使用 `CC BY-NC-SA 3.0` 协议,转载请注明地址。
136+
本书使用 `CC BY-NC-SA 4.0` 协议,转载请注明地址。
137137

138138
## gitbook生成
139139

SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* [emacs](./03-editors/03-03-emacs.md)「tiansiyuan」
1212
* [vscode](./03-editors/03-04-vscode.md)「daogangtang」
1313
* [atom](./03-editors/03-05-atom.md)「wayslog」
14-
* [atom](./03-editors/03-06-sublime.md)
14+
* [sublime](./03-editors/03-06-sublime.md)
1515
* [visual studio](./03-editors/03-07-visualstudio.md)「marvinguo」
1616
* [spacemacs](./03-editors/03-10-spacemacs.md)「wayslog」
1717
* [Rust一小时快速入门](./04-quickstart/04-00-intro.md)「ee0703」

0 commit comments

Comments
 (0)