|
|
|
@ -208,6 +208,59 @@ fn main() {
|
|
|
|
|
从之前的线程结束规则,我们可以猜测程序将这样执行:`A`线程结束后,由它创建的`B`线程仍在疯狂输出,直到`main`线程在100毫秒后结束。如果你把该时间增加到几十秒,就可以看到你的CPU核心100%的盛况了-,-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 多线程的性能
|
|
|
|
|
下面我们从多个方面来看看多线程的性能大概是怎么样的。
|
|
|
|
|
|
|
|
|
|
#### 创建线程的性能
|
|
|
|
|
据不精确估算,创建一个线程大概需要0.24毫秒,随着线程快速创建时,这个值会变得更大,因此线程的创建耗时并不是不可忽略的,只有当真的需要处理一个值得用线程去处理的任务时,才使用线程。一些鸡毛蒜皮的任务,就无需创建线程了。
|
|
|
|
|
|
|
|
|
|
#### 创建多少线程合适
|
|
|
|
|
因为CPU的核心数限制,当任务是密集型时,就算线程数超过了CPU核心数,也并不能帮你获得更好的性能,因为每个线程的任务都可以轻松让CPU的某个核心跑满,既然如此,让线程数等于CPU核心数是最好的。
|
|
|
|
|
|
|
|
|
|
但是当你的任务大部分时间都处于阻塞状态时,就可以考虑增多线程数量,这样当某个线程处于阻塞状态时,会被切走,进而运行其它的线程,典型就是网络IO操作,我们可以为每一个进来的用户连接创建一个线程去处理,该连接绝大部分时间都是处于IO读取阻塞状态,因此有限的CPU核心完全可以处理成百上千的用户连接线程,但是事实上,对于这种网络IO情况,一般都不再使用多线程的方式了,毕竟操作系统的线程数是有限的,意味着并发数也很容易达到上限,使用async/await的`M:N`并发模型,就没有这个烦恼。
|
|
|
|
|
|
|
|
|
|
#### 多线程的开销
|
|
|
|
|
下面的代码是一个lock-free的hashmap在多线程下的使用:
|
|
|
|
|
```rust
|
|
|
|
|
for i in 0..num_threads {
|
|
|
|
|
//clone the shared data structure
|
|
|
|
|
let ht = Arc::clone(&ht);
|
|
|
|
|
|
|
|
|
|
let handle = thread::spawn(move || {
|
|
|
|
|
for j in 0..adds_per_thread {
|
|
|
|
|
//randomly generate and add a (key, value)
|
|
|
|
|
let key = thread_rng().gen::<u32>();
|
|
|
|
|
let value = thread_rng().gen::<u32>();
|
|
|
|
|
ht.set_item(key, value);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
handles.push(handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for handle in handles {
|
|
|
|
|
handle.join().unwrap();
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
按理来说,既然是`lock-free`了,那么锁的开销应该很小,性能会随着线程数的增加几近线程增长,但是真的是这样吗?
|
|
|
|
|
|
|
|
|
|
下图是该代码在`48`核机器上的运行结果:
|
|
|
|
|
|
|
|
|
|
<img alt="" src="/img/threads-02.png" class="center" />
|
|
|
|
|
|
|
|
|
|
从图上可以明显的看出: 吞吐并不是线性增长,其次从16核开始,甚至开始肉眼可见的下降,这是为什么呢?
|
|
|
|
|
|
|
|
|
|
大概原因如下:
|
|
|
|
|
|
|
|
|
|
- 虽然是无锁,但是内部是CAS实现,大量线程的同时访问,会让CAS重试次数大幅增加
|
|
|
|
|
- 线程过多时,CPU缓存的命中率会显著下降, 同时多个线程竞争一个CPU Cache-line的情况也会经常发生
|
|
|
|
|
- 大量读写可能会让内存带宽也成为瓶颈
|
|
|
|
|
- 读和写不一样,无锁数据结构的读往往可以很好的线性增长,但是写不行,因为写竞争太大
|
|
|
|
|
|
|
|
|
|
总之,多线程的开销往往是在锁、数据竞争、缓存失效上,这些限制了现代化软件系统随着CPU核心的增多性能也线性增加的野心。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
|
[Rust的线程模型](./intro.md)是`1:1`模型,因为Rust要保持尽量小的运行时。
|
|
|
|
|
|
|
|
|
|