add多线程使用

pull/157/head
sunface 3 years ago
parent ca47c23374
commit bb214077e4

@ -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要保持尽量小的运行时。

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -1,4 +1,6 @@
# performance
## How do I profile a Rust web application in production?
https://www.reddit.com/r/rust/comments/rupcux/how_do_i_profile_a_rust_web_application_in/
https://www.reddit.com/r/rust/comments/rupcux/how_do_i_profile_a_rust_web_application_in/
https://zhuanlan.zhihu.com/p/191655266
Loading…
Cancel
Save