放在十年前,多线程编程可能还是一个少数人才掌握的核心概念,但是在今天,随着编程语言的不断发展,多线程、多协程、Actor 等并发编程方式已经深入人心,同时多线程编程的门槛也在不断降低,本章节我们来看看在 Rust 中该如何使用多线程。
Rust并发编程
193 views
放在十年前,多线程编程可能还是一个少数人才掌握的核心概念,但是在今天,随着编程语言的不断发展,多线程、多协程、Actor 等并发编程方式已经深入人心,同时多线程编程的门槛也在不断降低,本章节我们来看看在 Rust 中该如何使用多线程。
在Rust多线程编程中,同步性极其的重要,当你需要同时访问一个资源、控制不同线程的执行次序时,都需要使用到同步性。
在 Rust 中有多种方式可以实现同步性。简单来说,无非两种实现方式:
今天我想分享使用共享内存的方式来实现多线程的通信:
// Mutex互斥锁
use std::sync::{Mutex, Arc,Condvar};
use std::thread::{self,sleep};
use std::sync::RwLock;
use std::time::Duration;
// 单线程使用mutex
fn single_mutex() {
// 使用`Mutex`结构体的关联函数创建新的互斥锁实例
let m = Mutex::new(5);
{
// 获取锁,然后deref为`m`的引用
// lock返回的是Result
let mut num = m.lock().unwrap(); // m.lock()向m申请一个锁, 该方法会阻塞当前线程,直到获取到锁,因此当多个线程同时访问该数据时,只有一个线程能获取到锁,其它线程只能阻塞着等待
// m.lock()方法也有可能报错,例如当前正在持有锁的线程panic了。在这种情况下,其它线程不可能再获得锁,因此lock方法会返回一个错误。
*num = 6; //准确的说是m.lock()返回一个智能指针MutexGuard<T>
// 锁自动被drop
}
println!("m = {:?}", m);
}
// 如果把上面的括号去掉,同时申请两把锁,就会出现死锁
fn dead_mutex() {
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); // m = Mutex { data: 7, poisoned: false, .. }
}
// 只能使用Arc<T>,实现多线程访问
fn mult_mutex() {
// 在使用数据前必须先获取锁
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap()); // Result: 10
}
fn main() {
single_mutex();
dead_mutex();
mult_mutex();
}
上面的代码大概跑下来耗时在2s左右,我在想这么久呀,于是我用Go跑了一个相同功能代码,测试下来耗时214ms左右。
package main
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
var N = 0
func add_one_thread(n uint64, w *sync.WaitGroup) {
mu.Lock()
defer mu.Unlock()
var i uint64 = 0
for i < n {
N += 1
i++
}
w.Done()
}
func main() {
var start = time.Now()
wg := &sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go add_one_thread(10000000, wg)
}
wg.Wait()
fmt.Println("cost time: ", time.Now().Sub(start))
fmt.Println("N: ", N)
}
为什么差距这么大哪,根据以往的经验,我在想可能是Go语言在运行时的内存等资源用的比较多,而Rust相对来说牺牲性能来赢得资源的节省吧,有兴趣的朋友可以测试一下两者的资源消耗。