• Rust语言之多线程



    一、简介

    多线程 是一种并发执行的技术,它允许一个程序或进程同时执行多个线程。每个线程都是程序执行的一个独立路径,它们可以并行运行,共享进程的资源(如内存空间),但每个线程有自己的指令指针、堆栈和局部变量。多线程的主要目的是提高程序的执行效率,通过同时执行多个任务来充分利用计算机的多核处理器。

    • 在Rust语言中,多线程编程是通过标准库中的std::thread模块来实现的。Rust提供了创建和管理线程的API,以及用于线程间同步和通信的机制,如互斥锁(Mutex)和通道(Channel)。

    二、创建线程

    1.创建一个线程

    use std::thread;  
      
    fn main() {  
        // 创建一个新线程  
        let handle = thread::spawn(|| {  
            // 在新线程中执行的代码  
            println!("Hello from a new thread!");  
        });  
      
        // 等待线程结束  
        handle.join().unwrap();  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.创建多个线程

    use std::thread;  
    
    fn main() {  
        // 创建一个向量来存储线程的句柄  
        let mut threads = vec![];  
        
        // 创建多个线程  
        for i in 0..5 {  
            // 使用闭包捕获变量i的值  
            let thread_number = i;  
            let handle = thread::spawn(move || {  
                // 在新线程中打印线程编号  
                println!("线程 {} 正在运行", thread_number);  
            });  
        
            // 将线程句柄添加到向量中  
            threads.push(handle);  
        }  
        
        // 等待所有线程完成  
        for handle in threads {  
            handle.join().unwrap();  
        }  
    
        // 在主线程中打印一些信息  
        for i in 0..5 {  
            println!("主线程打印数字: {}", i);  
        }  
    }
    
    # 输出结果:
    线程 0 正在运行
    线程 1 正在运行
    线程 2 正在运行
    线程 3 正在运行
    线程 4 正在运行
    主线程打印数字: 0
    主线程打印数字: 1
    主线程打印数字: 2
    主线程打印数字: 3
    主线程打印数字: 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 从输出结果上看,仍然像是顺序执行,所以这里引入一个休眠,让线程执行的时候随机休眠0-3秒。

    生成随机数

    由于Rust核心语言中没有随机数生成的函数,需要使用rand库来进行

    # 首先需要在Cargo.toml中添加以下内容
    [dependencies]  
    rand = "0.8"
    
    # 然后在代码中用use 引入
    use rand::Rng;  
    use rand::thread_rng;  
      
    fn main() {  
        let mut rng = thread_rng();  
        for _i in 0..10{
            let random_number = rng.gen_range(1..4);  
            println!("随机数是: {}", random_number);  
        }
    }
    # 结果:
    随机数是: 3
    随机数是: 1
    随机数是: 3
    随机数是: 1
    随机数是: 3
    随机数是: 1
    随机数是: 3
    随机数是: 2
    随机数是: 3
    随机数是: 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    随机数生成的区间与循环一样,是一个前闭后开的区间

    尝试让程序睡一会儿

    use rand::Rng;  
    use std::{thread::sleep, time::Duration};  
      
    fn main() {  
        // 创建一个随机数生成器  
        let mut rng = rand::thread_rng();  
        // 生成一个0到3之间的随机秒数  
        let random_seconds: u64 = rng.gen_range(0..4);  
        // 将秒数转换为Duration  
        let duration = Duration::from_secs(random_seconds);  
        // 让当前线程睡眠指定的时间  
        sleep(duration);  
        // 之后的代码会在等待后执行  
        println!("等待了 {} 秒", random_seconds);  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    引入多线程

    use rand::Rng;  
    use std::{thread::sleep, time::Duration};  
    use std::thread;  
    
    fn main() {  
        // 创建一个向量来存储线程的句柄  
        let mut threads = vec![];  
        
        // 创建多个线程  
        for i in 1..=10 {  
            // 使用闭包捕获变量i的值  
            let thread_number = i;  
            let handle = thread::spawn(move || {  
                // 在新线程中打印线程编号  
                println!("线程 {} 正在运行", thread_number);      
                // 创建一个随机数生成器  
                let mut rng = rand::thread_rng();  
                // 生成一个0到3之间的随机秒数  
                let random_seconds: u64 = rng.gen_range(0..4);  
                // 将秒数转换为Duration  
                let duration = Duration::from_secs(random_seconds);  
                // 让当前线程睡眠指定的时间  
                sleep(duration);  
                println!("线程 {} 运行结束,休息了{}秒.", thread_number,random_seconds);  
            });  
        
            // 将线程句柄添加到向量中  
            threads.push(handle);  
        }  
        
        // 等待所有线程完成  
        for handle in threads {  
            handle.join().unwrap();  
        }  
    
        // 在主线程中打印一些信息  
        for i in 0..5 {  
            println!("主线程打印数字: {}", i);  
        }  
    }
    
    # 输出结果
    线程 3 正在运行
    线程 2 正在运行
    线程 5 正在运行
    线程 7 正在运行
    线程 7 运行结束,休息了0.
    线程 4 正在运行
    线程 6 正在运行
    线程 1 正在运行
    线程 8 正在运行
    线程 9 正在运行
    线程 10 正在运行
    线程 6 运行结束,休息了1.
    线程 4 运行结束,休息了1.
    线程 3 运行结束,休息了1.
    线程 9 运行结束,休息了1.
    线程 1 运行结束,休息了2.
    线程 10 运行结束,休息了2.
    线程 5 运行结束,休息了2.
    线程 2 运行结束,休息了3.
    线程 8 运行结束,休息了3.
    主线程打印数字: 0
    主线程打印数字: 1
    主线程打印数字: 2
    主线程打印数字: 3
    主线程打印数字: 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    三、线程返回值的处理

    对于有返回值的多线程来说有两种情况,一种是每个线程处理一个独立的值,用向量接收,另一种是多个线程处理一个值。

    1.每个线程处理一个独立的值

    use std::thread;  
      
    fn main() {  
        let mut handles = vec![];  
      
        for i in 0..5 {  
            let handle = thread::spawn(move || {  
                return i*i;
            });  
            handles.push(handle);  
        }  
      
        let mut results = vec![];  
      
        for handle in handles {  
            match handle.join() {  
                Ok(value) => results.push(value),  
                Err(e) => println!("Thread panicked: {:?}", e),  
            }  
        }  
      
        println!("Results: {:?}", results);  //Results: [0, 1, 4, 9, 16]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.多个线程处理一个值

    由于多个线程处理一个值,可能造成条件竞争,属于线程不安全行为,Rust语言中提供了3种处理行为。

    • Arc 只读访问,用于共享只读数据,通过原子引用计数管理生命周期。
    • Mutex 互斥锁,用于保护数据,确保一次只有一个线程可以访问数据(提供独占访问)。
    • RwLock 读写锁,用于保护数据,但允许多个读者同时访问,写者必须独占访问。

    Arc(原子引用计数)

    Arc是一个提供共享所有权的智能指针。它用于在多个所有者之间共享数据,且只允许对这些数据进行只读访问。Arc通过原子操作维护一个引用计数,确保数据的生命周期至少与最长的所有者一样长。当最后一个Arc指针被丢弃时,其指向的数据也会被释放。

    use std::sync::Arc;  
    use std::thread;  
      
    fn main() {  
        // 创建一个要在多个线程之间共享的值  
        let data = Arc::new(vec![1, 2, 3, 4, 5]);  
      
        // 创建一个向量来存储线程的句柄  
        let mut handles = vec![];  
        println!("Thread {:?} is reading value: {:?}", thread::current().id(), &data); // 主线程的线程ID为 1
        // 创建几个线程来只读访问数据  
        for _i in 0..data.len() {  
            let data = data.clone(); // 克隆Arc以便在线程中使用  
            let handle = thread::spawn(move || {  
                // 获取Vec的引用以便索引  
                // 使用 {:?} 来打印 ThreadId  
                println!("Thread {:?} is reading value: {:?}", thread::current().id(), &data);  
            });  
            handles.push(handle);  
        }  
      
        // 等待所有线程完成  
        for handle in handles {  
            handle.join().unwrap();  
        }  
    }
    # 运行结果
    Thread ThreadId(1) is reading value: [1, 2, 3, 4, 5]
    Thread ThreadId(2) is reading value: [1, 2, 3, 4, 5]
    Thread ThreadId(3) is reading value: [1, 2, 3, 4, 5]
    Thread ThreadId(4) is reading value: [1, 2, 3, 4, 5]
    Thread ThreadId(5) is reading value: [1, 2, 3, 4, 5]
    Thread ThreadId(6) is reading value: [1, 2, 3, 4, 5]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    Mutex(互斥锁)

    Mutex是一个提供互斥访问的智能指针。它用于保护数据,确保一次只有一个线程能够访问数据。当一个线程拥有Mutex的锁时,其他尝试获取锁的线程将被阻塞,直到锁被释放。

    use std::sync::{Arc, Mutex};  
    use std::thread;  
      
    fn main() {  
        // 创建一个Arc包裹的互斥锁和值  
        let counter = Arc::new(Mutex::new(1));  
        let mut handles = vec![];  
      
        // 创建几个线程来增加计数器  
        for i in 1..10 {  
            // 克隆Arc智能指针,而不是Mutex或它的值  
            let counter = Arc::clone(&counter);  
            let handle = thread::spawn(move || {  
                // 获取互斥锁以便修改值  
                let mut num = counter.lock().unwrap();  
                *num *=i;
            });  
            handles.push(handle);  
        }  
      
        // 等待所有线程完成  
        for handle in handles {  
            handle.join().unwrap();  
        }  
      
        // 输出最终计数器的值  
        println!("Result: {}", *counter.lock().unwrap());  // Result: 362880
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    RwLock(读写锁)

    RwLock是一个提供读写锁定的智能指针。与Mutex不同,RwLock允许多个读者同时访问数据,但写者必须独占锁。当写者拥有锁时,任何尝试获取读锁或写锁的线程都将被阻塞。当没有写者时,可以有多个读者同时访问数据。

    use std::sync::{Arc, RwLock};  
      
    fn main() {  
        let data = Arc::new(RwLock::new(0));  
        let mut handles = vec![];  
      
        // 创建多个读线程  
        for i in 0..5 {  
            let data = Arc::clone(&data);  
            let handle = std::thread::spawn(move || {  
                let num = data.read().unwrap();  
                println!("Thread {} Reading value: {}", i,*num);  
            });  
            handles.push(handle);  
        }  
      
        // 创建一个写线程  
        let data = Arc::clone(&data);  
        let handle = std::thread::spawn(move || {  
            let mut num = data.write().unwrap();  
            *num += 1;  
            println!("Writing value: {}", *num);  
        });  
        handles.push(handle);  
      
        for handle in handles {  
            handle.join().unwrap();  
        }  
    }
    # 第一次执行结果
    Thread 0 Reading value: 0
    Thread 3 Reading value: 0
    Thread 1 Reading value: 0
    Thread 4 Reading value: 0
    Thread 2 Reading value: 0
    Writing value: 1
    # 第二次执行结果
    Thread 0 Reading value: 0
    Thread 3 Reading value: 0
    Thread 1 Reading value: 0
    Thread 4 Reading value: 0
    Thread 2 Reading value: 0
    Writing value: 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    这里有一个问题,就是如果写线程最后执行,那么读线程读的都是原始数据,如果写线程先执行,那么读的就是修改后的数据,所以对读写顺序有要求的话应该做好时序的控制

  • 相关阅读:
    高阶数据结构学习之LRU_Cache
    自动驾驶仿真:VTD调用罗技 G923方向盘(Linux环境)
    Java 面试题集锦,横扫金九银十。
    DevOps平台建设的关键点是什么?
    RL 暂态电路与磁能
    DIV布局个人介绍网页模板代码 家乡海阳个人简介网页制作 简单个人静态HTML网页设计作品 DW个人网站制作成品 web网页制作与实现
    wxPython 布局调试技巧
    645仪表以JSON格式上发方法
    vue3-video-play视频播放组件
    基于SSM的二手车交易网站的设计与实现
  • 原文地址:https://blog.csdn.net/weixin_45963786/article/details/136194236