• Rust:多线程并发编程


    线程

    线程(thread)是一个程序中独立运行的一个部分。代码程序运行在进程里,代码又可以分成多个部分(多个线程)来运行。
    线程不同于进程(process)的地方是线程是程序以内的概念,程序往往是在一个进程中执行的。
    在有操作系统的环境中进程往往被交替地调度得以执行,线程则在进程以内由程序进行调度。
    由于线程并发很有可能出现并行的情况,所以在并行中可能遇到的死锁、延宕错误常出现于含有并发机制的程序。
    为了解决这些问题,很多其它语言(如 Java、C#)采用特殊的运行时(runtime)软件来协调资源,但这样无疑极大地降低了程序的执行效率。
    C/C++ 语言在操作系统的最底层也支持多线程,且语言本身以及其编译器不具备侦察和避免并行错误的能力,这对于开发者来说压力很大,开发者需要花费大量的精力避免发生错误。
    Rust 不依靠运行时环境,这一点像 C/C++ 一样。
    但 Rust 在语言本身就设计了包括所有权机制在内的手段来尽可能地把最常见的错误消灭在编译阶段,这一点其他语言不具备。

    多线程会导致竞争状态、死锁、产生一些只在某些情况发生的bug,难以复现,难以修复。

    创建线程

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

    use std::thread;
    use std::time::Duration;
    fn main() {
        fn spawn_function() {
        for i in 0..5 {
            println!("spawned thread print {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    }
        thread::spawn(spawn_function);
        for i in 0..3 {
            println!("main thread print {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    当主线程走完的时候,不管子线程是否运行完,都结束程序。

    use std::thread;
    fn main() {
        let handle=thread::spawn(|| {
            "Hello from a thread!"
        });
        println!("{}", handle.join().unwrap());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以使用join()方法来等待线程的完成,会阻止当前线程的运行,知道handle所表示的线程的终结。

    move闭包

    闭包是可以保存进变量或作为参数传递给其他函数的匿名函数。闭包相当于 Rust 中的 Lambda 表达式,格式如下:
    |参数1, 参数2, …| -> 返回值类型 {
    // 函数体
    }
    move闭包通常和thread::spawn函数一起使用,它允许你使用其它线程的数据,创建线程时,把值的所有权从一个线程转移到另一个线程。

    use std::thread;
    fn main(){
        let v=vec![1,2,3];
        let handle=thread::spwan(move||{
            println!("here a vector:{:?},"v);
            });
            handle.join().unwrap();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    Rust 中一个实现消息传递并发的主要工具是通道(channel),通道有两部分组成,一个发送者(transmitter)和一个接收者(receiver)。
    std::sync::mpsc 包含了消息传递的方法:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    recv方法:阻止当前线程执行,直到channel中有值被传进来。
    send之后,val的所有权就被转移走了,当前线程无法使用val。

    发送多个值,看到接收者在等待。

    use std::sync::mpsc;
    use std::thread;
    use std::time::Duration;
    
    fn main(){
        let (tx,rx)=mpsc::channel();
        thread::spawn(move||{
            let vals=vec![
                String::from("hi"),
                String::from("from"),
                String::from("the"),
                String::from("thread"),
            ];
            for val in vals{
                tx.send(val).unwrap();
                thread::sleep(Duration::from_millis(200));
            }
        });
        for recived in rx{
            println!("Got:{}",recived);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    共享状态的并发

    channel类似单所有权:一旦将值的所有权转移至channel,就无法使用它了。
    共享内存并发类似多所有权:多个线程可以同时访问同一块内存。
    在rust里使用mutex来每次只允许一个线程来访问数据。lock数据结构是mutex的一部分,它能跟踪谁对数据拥有独占访问权。

    mutex的两条规则:在使用数据之前,必须尝试获得锁(lock)。
    使用完mutex所保护的数据,必须对数据进行解锁,以便其他进程可以获取锁。

    use std::sync::Mutex;
    
    fn main(){
        let m=Mutex::new(5);
        {
            let mut num=m.lock().unwrap();
            *num=6;
        }
        println!("m={:?}",m);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    多线程共享Mutex

    use std::sync::Mutex;
    use std::thread;
    
    fn main(){
        let counter=Mutex::new(0);
        let mut handles=vec![];
        for _ in 0..10{
            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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    上面这段代码会报错,因为第一个线程将counter所有权移动到里面,第二个还想使用移动,这时就会报错。
    如何将这个所有权移动到多个线程中呢?
    多线程的多重所有权。
    使用Arc来进行原子引用计数。

    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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用send和sync来扩展并发

    send:允许线程间转移所有权
    sync:允许多线程同时访问

  • 相关阅读:
    对账、结账、错账更正方法、划线更正法、红字更正法、补充登记法
    MacBooster8全新Mac系统清理优化工具
    Windows 桌面窗口管理器
    漏洞危害之一
    [Linux](14)信号
    C++之内建函数对象
    【SpringBoot应用篇】SpringBoot集成Caffeine本地缓存
    vue computed作用特点及使用场景及示例
    万字长文:FineBI面试题及参考答案详解
    BBR 会取代 CUBIC 吗?
  • 原文地址:https://blog.csdn.net/phthon1997/article/details/125897531