rust多线程


多线程可能导致的问题

  • 竞争
  • 死锁
  • Bug难以复现和修复

实现线程的方式

  • OS的API,1:1模型
  • 语言自己的线程,M:N模型

Rust标准库仅提供了1:1线程的模型。

通过thread::spawn函数创建新线程

let handle = thread::spawn(|| {
    for i in 1..=10 {
        println!("spawn thread: {}",i);
        thread::sleep(Duration::from_millis(1));
    }
});

for i in 1..5 {
    println!("main thread: {}",i);
    thread::sleep(Duration::from_millis(1));
}

// 交替打印,主线程结束后,整个程序结束。

// 等待子线程执行完毕
handle.join();

move闭包

允许在当前线程使用其他线程的数据。

let v = vec![1,2,3];

let handle = thread::spawn(move || {

    // v不能直接用是因为目前线程执行时,
    // v不一定还在,例如2处。
    println!("{:?}",v); // [1,2,3]
});

// 2
// drop(v);

handle.join().unwrap();

使用消息传递跨线程传递数据

channel

包含发送端和接收端。

mpsc::channel 创建,multi producer, single consumer.

use std::{sync::mpsc, thread};

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        thread::spawn(move || {
            let val = String::from("hi");
            tx.send(val).unwrap(); // 移交了所有权
            //println!("{}",val);
              //            ^^^ value borrowed here after move
        });
    });

    // recv 阻止线程执行,返回Resule<T,E>
    // 当发送端关闭,会收到一个错误
    let received = rx.recv().unwrap();
    println!("Got: {}", received); // Got hi
}

发送接收多个数据

use std::{sync::mpsc, thread, time::Duration};

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("hello"),
            String::from("ha "),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_millis(1));
        }
    });

    for received in rx {
        println!("{}",received);
    }
}

克隆多个sender

use std::{sync::mpsc, thread, time::Duration};

fn main() {
    let (tx, rx) = mpsc::channel();


    let tx1 = mpsc::Sender::clone(&tx);
    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("hello"),
            String::from("ha "),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_millis(10000));
        }
    });

    thread::spawn(move || {
        let vals = vec![
            String::from("1: hi"),
            String::from("1: hello"),
            String::from("1: ha "),
        ];

        for val in vals {
            tx1.send(val).unwrap();
            thread::sleep(Duration::from_millis(1));
        }
    });

    for received in rx {
        println!("{}",received);
    }
}

通过共享实现并发

多个线程可以同时访问同一个线程。使用mutex来允许一个线程来访问数据。

image-20221205120753972

多线程共享Mutex

多线程的多重所有权问题。

Arc<T>Rc<T>类似,可以用于并发场景。A: atomic。

use std::{sync::{Mutex, Arc}, 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!("{:?}", counter); // Mutex { data: 10, poisoned: false, .. }
}

Send和Sync trait

Send: 允许线程间转移所有权。

Sync: 可以安全被多个线程引用。

面向对象

满足了面向对象的定义,如struct,impl。

Rust没有继承

使用继承的原因:

  • 代码复用
    • Rust默认trait方法来实现代码共享
  • 多态
    • Rust泛型和trait约束。

使用Triat对象存储不同类型的值

trait用来定义共有方法。

模式匹配

模式是Rust的一种特殊语法,用于匹配复杂和简单类型的结构。

能够匹配任何可能传递的值得模式:无可辩驳的

let x = 5;

对某些可能的值无法进行匹配的模式:可辩驳的

if let Some(x) = a_value

Unsafe Rust

没有强制内存安全保证。

存在原因:

  • 静态分析保守,使用unsafe自担风险
  • 计算机硬件本身不安全,Rust需要进行底层编程

unsafe并没有关闭借用检查。

unsafe可执行的四个动作:

  • 解引用原始指针
  • 调用unsafe函数
  • 访问或修改可变的静态变量
  • 实现unsafe trait

解引用原始指针:

Raw pointer

不同于引用,原始指针

  • 允许为null
  • 不实现自动清理
  • 不保证指向合理内存
  • 允许不可变和可变指针或多个可变指针忽略借用规则
fn main() {
    let mut num = 45; 
    
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("{}", *r1); // 45
        println!("{}", *r2); // 45
    }   
}

外部函数

image-20221205164408123

改变静态变量:

static mut COUNTER: u32 = 0;
// 静态变量,声明要标注类型,只能'static生命周期,无需显示标注
// 有固定的内存地址。

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);
    unsafe {
        println!("{}",COUNTER);
    }
    
}

关联类型

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

高级类型

Never类型,无法产生可供返回的值。

例如:

let guess = "";

loop {
    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => continue,
    };
}

或者

loop {
    print!("forever");
}

永远不会终止,返回的就是never类型。

动态大小

运行时才能确定字符串的长度。

引用、trait对象、sized triat。

函数指针

fn main() {
    let answer = do_twice(add_one, 5);

    println!("answer: {}", answer);
}

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

原理比较复杂,用到再说。

macro

指一组相关特性的集合称谓。

  • 使用macro_rules!构建的声明宏
  • 三种过程宏
    • 自定义#[derive]宏
    • 类似属性的宏,可以在任何条目上添加自定义属性
    • 类似函数的宏,看起来像函数调用

函数和宏的区别

  • 宏可以编写生成其他代码的代码
  • 函数定义签名要声明参数个数和类型,宏可处理可变的参数
  • 编译器在解释代码前会展开宏
  • 宏的定义比函数复杂,难以阅读理解。