Rust并发编程

放在十年前,多线程编程可能还是一个少数人才掌握的核心概念,但是在今天,随着编程语言的不断发展,多线程、多协程、Actor 等并发编程方式已经深入人心,同时多线程编程的门槛也在不断降低,本章节我们来看看在 Rust 中该如何使用多线程。

193 views
Comments
登录后评论
Sign In
·

在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();
}
·

Rust冲!

·

上面的代码大概跑下来耗时在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相对来说牺牲性能来赢得资源的节省吧,有兴趣的朋友可以测试一下两者的资源消耗。

·

详细的网址:https://blog.csdn.net/Unicorn_wan/article/details/125088170