美文网首页
【RUST_BASIC】Rust 并发

【RUST_BASIC】Rust 并发

作者: ixiaolong | 来源:发表于2021-11-25 17:30 被阅读0次

    1 线程

    调用 thread::spawn 函数创建线程并传递一个闭包,包含新线程运行的代码:

    use std::thread;
    use std::time::Duration;
    
    fn main() {
        thread::spawn(|| {
            for i in 1..10 {
                println!("hi number {} from the spawned thread!", i);
                thread::sleep(Duration::from_millis(1));
            }
        });
    
        for i in 1..5 {
            println!("hi number {} from the main thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    }
    

    使用 join 等待所有线程结束:

    use std::thread;
    use std::time::Duration;
    
    fn main() {
        let handle = thread::spawn(|| {
            for i in 1..10 {
                println!("hi number {} from the spawned thread!", i);
                thread::sleep(Duration::from_millis(1));
            }
        });
    
        for i in 1..5 {
            println!("hi number {} from the main thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    
        handle.join().unwrap();
    }
    

    move 闭包经常与 thread::spawn 一起使用,允许在一个线程中使用另一个线程的数据。使用 move 关键字强制闭包获取其使用的环境值的所有权,在创建新线程将值的所有权从一个线程移动到另一个线程,避免引用的数据变为无效:

    use std::thread;
    
    fn main() {
        let v = vec![1, 2, 3];
    
        let handle = thread::spawn(move || {
            println!("Here's a vector: {:?}", v);
        });
    
        handle.join().unwrap();
    }
    

    2 消息传递

    Rust 中一个实现消息传递并发的主要工具是通道(channel),由发送者(transmitter)和接收者(receiver)组成。可以使用 mpsc::channel 函数创建一个新的通道,mpsc多个生产者,单个消费者(multiple producer, single consumer)的缩写。mpsc::channel 函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。

    use std::thread;
    use std::sync::mpsc;
    
    fn main() {
        let (tx, rx) = mpsc::channel();
    
        thread::spawn(move || {
            let val = String::from("hi");
            tx.send(val).unwrap();
        });
    
        let received = rx.recv().unwrap();
        println!("Got: {}", received);
    }
    

    3 共享内存

    在某种程度上,任何编程语言中的通道都类似于单所有权,因为一旦将一个值传送到通道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。

    互斥器(mutex)是 mutual exclusion 的缩写,任意时刻只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的(lock)来表明其希望访问数据,使用时需要注意如下:

    • 在使用数据之前尝试获取锁;
    • 处理完被互斥器所保护的数据之后,必须解锁数据;

    单线程使用如下:

    use std::sync::Mutex;
    
    fn main() {
        let m = Mutex::new(5);
    
        {
            let mut num = m.lock().unwrap();
            *num = 6;
        }
    
        println!("m = {:?}", m);
    }
    

    Mutex<T> 是一个智能指针,lock() 返回 MutexGuard 的智能指针,这个智能指针实现了 Deref 指向其内部数据,也提供了一个 Drop 实现当 MutexGuard 离开作用域时自动释放锁。

    多线程使用如下:

    use std::sync::{Mutex, Arc};
    use std::thread;
    
    fn main() {
        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());
    }
    

    这里的 counter 使用了 Arc<T> 进行包装,原因如下:

    1. counter 需要让多个线程进获得所有权,需要使用智能指针;
    2. Rc<T> 不能安全的在线程间共享,当 Rc<T> 管理引用计数时,必须在每一个 clone 调用时增加计数,并在每一个克隆被丢弃时减少计数,但是 Rc<T> 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值;Arc<T> 类似 Rc<T> 并且可以安全的用于并发环境的类型,字母 “a” 代表原子性(atomic),所以这是一个原子引用计数(atomically reference counted)类型。其原子性会带有性能的消耗。

    4 Send trait 和 Sync trait

    Send trait 表明类型的所有权可以在线程间传递。几乎所有的 Rust 类型都是Send 的,不过有一些例外,包括 Rc<T>,因为如果克隆了 Rc<T> 的值并尝试将克隆的所有权转移到另一个线程,这两个线程都可能同时更新引用计数,为此,Rc<T> 被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价,而使用标记为 SendArc<T> 时,就没有问题了。任何完全由 Send 的类型组成的类型也会自动被标记为 Send,几乎所有基本类型都是 Send 的。

    Sync trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用。换一种方式来说,对于任意类型 T,如果 &TSend 的话 T 就是 Sync 的,这意味着其引用就可以安全的发送到另一个线程。类似于 Send 的情况,基本类型是 Sync 的,完全由 Sync 的类型组成的类型也是 Sync 的。

    通常并不需要手动实现 SendSync trait,因为由 SendSync 的类型组成的类型,自动就是 SendSync 的。他们只是用来加强并发相关的不可变性的。手动实现这些标记 trait 涉及到编写不安全的 Rust 代码。

    参考

    https://kaisery.github.io/trpl-zh-cn/ch16-00-concurrency.html

    相关文章

      网友评论

          本文标题:【RUST_BASIC】Rust 并发

          本文链接:https://www.haomeiwen.com/subject/dokvtrtx.html