美文网首页
【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