| 
						
						
							
								
							
						
						
					 | 
					 | 
					@ -34,7 +34,7 @@ fn main() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					有几点值得注意:
 | 
					 | 
					 | 
					 | 
					有几点值得注意:
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					- 线程内部的代码使用闭包来执行
 | 
					 | 
					 | 
					 | 
					- 线程内部的代码使用闭包来执行
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					- `main` 线程一旦结束,程序就立刻结束,因此需要保持它的存活,直到其它子线程完成自己的任务
 | 
					 | 
					 | 
					 | 
					- `main` 线程一旦结束,程序就立刻结束,因此需要保持它的存活,直到其它子线程完成自己的任务
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					- `thread::sleep` 会让当前线程休眠指定的时间,随后其它线程会被调度运行(上一节并发与并行中有简单介绍过),因此就算你的电脑只有一个 CPU 核心,该程序也会表现的如同多 CPU 核心一般,这就是并发!
 | 
					 | 
					 | 
					 | 
					- `thread::sleep` 会让当前线程休眠指定的时间,随后其它线程会被调度运行(上一节并发与并行中有简单介绍过),因此就算你的电脑只有一个 CPU 核心,该程序也会表现的如同多 CPU 核心一般,这就是并发!
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					来看看输出:
 | 
					 | 
					 | 
					 | 
					来看看输出:
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```console
 | 
					 | 
					 | 
					 | 
					```console
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -91,7 +91,7 @@ hi number 4 from the main thread!
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					以上输出清晰的展示了线程阻塞的作用,如果你将 `handle.join` 放置在 `main` 线程中的 `for` 循环后面,那就是另外一个结果:两个线程交替输出。
 | 
					 | 
					 | 
					 | 
					以上输出清晰的展示了线程阻塞的作用,如果你将 `handle.join` 放置在 `main` 线程中的 `for` 循环后面,那就是另外一个结果:两个线程交替输出。
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					## 在线程闭包中使用 move
 | 
					 | 
					 | 
					 | 
					## 在线程闭包中使用 move
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					在[闭包章节](../../advance/functional-programing/closure.md#move和Fn)中,有讲过 `move` 关键字在闭包中的使用可以让该闭包拿走环境中某个值的所有权,同样地,你可以使用 `move` 来将所有权从一个线程转移到另外一个线程。
 | 
					 | 
					 | 
					 | 
					在[闭包](https://course.rs/advance/functional-programing/closure.html#move-和-fn)章节中,有讲过 `move` 关键字在闭包中的使用可以让该闭包拿走环境中某个值的所有权,同样地,你可以使用 `move` 来将所有权从一个线程转移到另外一个线程。
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					首先,来看看在一个线程中直接使用另一个线程中的数据会如何:
 | 
					 | 
					 | 
					 | 
					首先,来看看在一个线程中直接使用另一个线程中的数据会如何:
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```rust
 | 
					 | 
					 | 
					 | 
					```rust
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -132,7 +132,7 @@ help: to force the closure to take ownership of `v` (and any other referenced va
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  |                                ++++
 | 
					 | 
					 | 
					 | 
					  |                                ++++
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```
 | 
					 | 
					 | 
					 | 
					```
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					其实代码本身并没有什么问题,问题在于 Rust 无法确定新的线程会活多久(多个线程的结束顺序并不是固定的),所以也无法确定新线程所引用的 `v` 是否在使用过程中一直合法:
 | 
					 | 
					 | 
					 | 
					其实代码本身并没有什么问题,问题在于 Rust 无法确定新的线程会活多久(多个线程的结束顺序并不是固定的),所以也无法确定新线程所引用的 `v` 是否在使用过程中一直合法:
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```rust
 | 
					 | 
					 | 
					 | 
					```rust
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					use std::thread;
 | 
					 | 
					 | 
					 | 
					use std::thread;
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -206,7 +206,7 @@ fn main() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					以上代码中,`main` 线程创建了一个新的线程 `A`,同时该新线程又创建了一个新的线程 `B`,可以看到 `A` 线程在创建完 `B` 线程后就立即结束了,而 `B` 线程则在不停地循环输出。
 | 
					 | 
					 | 
					 | 
					以上代码中,`main` 线程创建了一个新的线程 `A`,同时该新线程又创建了一个新的线程 `B`,可以看到 `A` 线程在创建完 `B` 线程后就立即结束了,而 `B` 线程则在不停地循环输出。
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					从之前的线程结束规则,我们可以猜测程序将这样执行:`A` 线程结束后,由它创建的 `B` 线程仍在疯狂输出,直到 `main` 线程在100毫秒后结束。如果你把该时间增加到几十秒,就可以看到你的 CPU 核心 100% 的盛况了-,-
 | 
					 | 
					 | 
					 | 
					从之前的线程结束规则,我们可以猜测程序将这样执行:`A` 线程结束后,由它创建的 `B` 线程仍在疯狂输出,直到 `main` 线程在 100 毫秒后结束。如果你把该时间增加到几十秒,就可以看到你的 CPU 核心 100% 的盛况了-,-
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					## 多线程的性能
 | 
					 | 
					 | 
					 | 
					## 多线程的性能
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -218,10 +218,10 @@ fn main() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					#### 创建多少线程合适
 | 
					 | 
					 | 
					 | 
					#### 创建多少线程合适
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					因为 CPU 的核心数限制,当任务是 CPU 密集型时,就算线程数超过了 CPU 核心数,也并不能帮你获得更好的性能,因为每个线程的任务都可以轻松让 CPU 的某个核心跑满,既然如此,让线程数等于 CPU 核心数是最好的。
 | 
					 | 
					 | 
					 | 
					因为 CPU 的核心数限制,当任务是 CPU 密集型时,就算线程数超过了 CPU 核心数,也并不能帮你获得更好的性能,因为每个线程的任务都可以轻松让 CPU 的某个核心跑满,既然如此,让线程数等于 CPU 核心数是最好的。
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					但是当你的任务大部分时间都处于阻塞状态时,就可以考虑增多线程数量,这样当某个线程处于阻塞状态时,会被切走,进而运行其它的线程,典型就是网络 IO 操作,我们可以为每一个进来的用户连接创建一个线程去处理,该连接绝大部分时间都是处于 IO 读取阻塞状态,因此有限的 CPU 核心完全可以处理成百上千的用户连接线程,但是事实上,对于这种网络 IO 情况,一般都不再使用多线程的方式了,毕竟操作系统的线程数是有限的,意味着并发数也很容易达到上限,而且过多的线程也会导致线程上下文切换的代价过大,使用 async/await 的 `M:N` 并发模型,就没有这个烦恼。
 | 
					 | 
					 | 
					 | 
					但是当你的任务大部分时间都处于阻塞状态时,就可以考虑增多线程数量,这样当某个线程处于阻塞状态时,会被切走,进而运行其它的线程,典型就是网络 IO 操作,我们可以为每一个进来的用户连接创建一个线程去处理,该连接绝大部分时间都是处于 IO 读取阻塞状态,因此有限的 CPU 核心完全可以处理成百上千的用户连接线程,但是事实上,对于这种网络 IO 情况,一般都不再使用多线程的方式了,毕竟操作系统的线程数是有限的,意味着并发数也很容易达到上限,而且过多的线程也会导致线程上下文切换的代价过大,使用 `async/await` 的 `M:N` 并发模型,就没有这个烦恼。
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					#### 多线程的开销
 | 
					 | 
					 | 
					 | 
					#### 多线程的开销
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					下面的代码是一个无锁实现(CAS)的 Hashmap 在多线程下的使用:
 | 
					 | 
					 | 
					 | 
					下面的代码是一个无锁实现(CAS)的 `Hashmap` 在多线程下的使用:
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```rust
 | 
					 | 
					 | 
					 | 
					```rust
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					for i in 0..num_threads {
 | 
					 | 
					 | 
					 | 
					for i in 0..num_threads {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    let ht = Arc::clone(&ht);
 | 
					 | 
					 | 
					 | 
					    let ht = Arc::clone(&ht);
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -248,7 +248,7 @@ for handle in handles {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					<img alt="" src="https://pic3.zhimg.com/80/v2-af225672de09c0e377023f5f39dd87eb_1440w.png" class="center"  />
 | 
					 | 
					 | 
					 | 
					<img alt="" src="https://pic3.zhimg.com/80/v2-af225672de09c0e377023f5f39dd87eb_1440w.png" class="center"  />
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					从图上可以明显的看出: 吞吐并不是线性增长,尤其从 `16` 核开始,甚至开始肉眼可见的下降,这是为什么呢?
 | 
					 | 
					 | 
					 | 
					从图上可以明显的看出:吞吐并不是线性增长,尤其从 `16` 核开始,甚至开始肉眼可见的下降,这是为什么呢?
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					限于书本的篇幅有限,我们只能给出大概的原因:
 | 
					 | 
					 | 
					 | 
					限于书本的篇幅有限,我们只能给出大概的原因:
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -333,9 +333,9 @@ FOO.with(|f| {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					});
 | 
					 | 
					 | 
					 | 
					});
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```
 | 
					 | 
					 | 
					 | 
					```
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					上面代码中,`FOO`即是我们创建的**线程局部变量**,每个新的线程访问它时,都会使用它的初始值作为开始,各个线程中的`FOO`值彼此互不干扰。注意`FOO`使用`static`声明为生命周期为`'static`的静态变量。
 | 
					 | 
					 | 
					 | 
					上面代码中,`FOO` 即是我们创建的**线程局部变量**,每个新的线程访问它时,都会使用它的初始值作为开始,各个线程中的 `FOO` 值彼此互不干扰。注意 `FOO` 使用 `static` 声明为生命周期为 `'static` 的静态变量。
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					可以注意到,线程中对`FOO`的使用是通过借用的方式,但是若我们需要每个线程独自获取它的拷贝,最后进行汇总,就有些强人所难了。
 | 
					 | 
					 | 
					 | 
					可以注意到,线程中对 `FOO` 的使用是通过借用的方式,但是若我们需要每个线程独自获取它的拷贝,最后进行汇总,就有些强人所难了。
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					你还可以在结构体中使用线程局部变量:
 | 
					 | 
					 | 
					 | 
					你还可以在结构体中使用线程局部变量:
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```rust
 | 
					 | 
					 | 
					 | 
					```rust
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -374,7 +374,7 @@ impl Bar {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```
 | 
					 | 
					 | 
					 | 
					```
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					#### 三方库 thread-local
 | 
					 | 
					 | 
					 | 
					#### 三方库 thread-local
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					除了标准库外,一位大神还开发了[thread-local](https://github.com/Amanieu/thread_local-rs)库,它允许每个线程持有值的独立拷贝:
 | 
					 | 
					 | 
					 | 
					除了标准库外,一位大神还开发了 [thread-local](https://github.com/Amanieu/thread_local-rs) 库,它允许每个线程持有值的独立拷贝:
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```rust
 | 
					 | 
					 | 
					 | 
					```rust
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					use thread_local::ThreadLocal;
 | 
					 | 
					 | 
					 | 
					use thread_local::ThreadLocal;
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					use std::sync::Arc;
 | 
					 | 
					 | 
					 | 
					use std::sync::Arc;
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -434,8 +434,8 @@ fn main() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					上述代码流程如下:
 | 
					 | 
					 | 
					 | 
					上述代码流程如下:
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					1. `main`线程首先进入`while`循环,调用`wait`方法挂起等待子线程的通知,并释放了锁`started`
 | 
					 | 
					 | 
					 | 
					1. `main` 线程首先进入 `while` 循环,调用 `wait` 方法挂起等待子线程的通知,并释放了锁 `started`
 | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					2. 子线程获取到锁,并将其修改为true, 然后调用条件变量的`notify_one`方法来通知主线程继续执行
 | 
					 | 
					 | 
					 | 
					2. 子线程获取到锁,并将其修改为 `true`,然后调用条件变量的 `notify_one` 方法来通知主线程继续执行
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					## 只被调用一次的函数
 | 
					 | 
					 | 
					 | 
					## 只被调用一次的函数
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					有时,我们会需要某个函数在多线程环境下只被调用一次,例如初始化全局变量,无论是哪个线程先调用函数来初始化,都会保证全局变量只会被初始化一次,随后的其它线程调用就会忽略该函数:
 | 
					 | 
					 | 
					 | 
					有时,我们会需要某个函数在多线程环境下只被调用一次,例如初始化全局变量,无论是哪个线程先调用函数来初始化,都会保证全局变量只会被初始化一次,随后的其它线程调用就会忽略该函数:
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -470,7 +470,7 @@ fn main() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					}
 | 
					 | 
					 | 
					 | 
					}
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					```
 | 
					 | 
					 | 
					 | 
					```
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					代码运行的结果取决于哪个线程先调用 `INIT.call_once` (虽然代码具有先后顺序,但是线程的初始化顺序并无法被保证!因为线程初始化是异步的,且耗时较久),若 `handle1` 先,则输出 `1`,否则输出 `2`。
 | 
					 | 
					 | 
					 | 
					代码运行的结果取决于哪个线程先调用 `INIT.call_once` (虽然代码具有先后顺序,但是线程的初始化顺序并无法被保证!因为线程初始化是异步的,且耗时较久),若 `handle1` 先,则输出 `1`,否则输出 `2`。
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					**call_once 方法**
 | 
					 | 
					 | 
					 | 
					**call_once 方法**
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -482,7 +482,7 @@ fn main() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					## 总结
 | 
					 | 
					 | 
					 | 
					## 总结
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					[Rust的线程模型](./intro.md)是 `1:1` 模型,因为 Rust 要保持尽量小的运行时。
 | 
					 | 
					 | 
					 | 
					[Rust 的线程模型](./intro.md)是 `1:1` 模型,因为 Rust 要保持尽量小的运行时。
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					我们可以使用 `thread::spawn` 来创建线程,创建出的多个线程之间并不存在执行顺序关系,因此代码逻辑千万不要依赖于线程间的执行顺序。
 | 
					 | 
					 | 
					 | 
					我们可以使用 `thread::spawn` 来创建线程,创建出的多个线程之间并不存在执行顺序关系,因此代码逻辑千万不要依赖于线程间的执行顺序。
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					 | 
					
 
 |