【一起学Rust | 基础篇】rust线程与并发

文章目录


前言

并发编程 (Concurrent programming),指的是程序的不同部分相互独立的执行。而并行编程 (parallel programming)代表程序不同部分于同时执行,这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因,在此类编程中一直是困难且容易出错的:Rust

希望能改变这一点。
在大部分现代操作系统中,已执行程序的代码在一个
进程 (process)中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。运行这些独立部分的功能被称为 线程(threads)。


一、创建线程

Rust创建线程是通过thread::spawn函数来创建的,我们只要通过这个函数,并且传入一个闭包即可创建出一个线程。

以两个线程同时输出数字为例,主线程输出1到5,子线程输出1到10。其代码如下所示

thread::spawn函数内的闭包就是子线程的内容,以外的全都是主线程的内容。
thread::sleep是一个线程延时的函数,需要传入DurationDuration是个表示时间单位的,这里Duration::from_millis(1)代表1毫秒,Rust支持更加精确的时间,你可以去看一下这个类,支持微秒纳秒等。

rust 复制代码
    thread::spawn(|| {
        for i in 1..10 {
            println!("数字是:{},来自spawn创建的线程", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    for i in 1..5 {
        println!("数字是:{},来自主线程线程", i);
        thread::sleep(Duration::from_millis(1));
    }

这样就创建好一个线程了,执行效果如下图
从图中可以看出主线程输出了从1到4的数字编号,因为到了5的时候已经执行完毕了,但是子线程却是执行到了5,并没有向后执行,这是因为主线程已经结束了,此时会把子线程也销毁,简而言之就是子线程的生存周期超出了主线程的生存周期,主线程提前结束。

原因:无法保证其执行顺序,主线程提前结束

为了解决这个,就需要接收这个线程的返回值,然后调用join方法来让主线程等待子线程执行完毕再结束运行。

thread::spawn返回一个JoinHandle,其有一个join方法,可以让主线程等待该线程完毕后再结束。

JoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。join 放在循环之前可以实现先执行子线程【这种操作会变得同步,影响程序运行,因此需要方队地方,仔细斟酌】

处理以上的问题,因此对代码进行一下改动

rust 复制代码
    let handle =thread::spawn(|| {
        for i in 1..10 {
            println!("数字是:{},来自spawn创建的线程", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    
    for i in 1..5 {
        println!("数字是:{},来自主线程线程", i);
        thread::sleep(Duration::from_millis(1));
    }
    handle.join().unwrap();

此时代码正常运行,结果如下图所示
如果将handle.join().unwrap();放到主线程for循环之前则会出现同步运行的效果,如下图所示

move关键字主要的作用是用来避免在使用闭包的时候遇到的所有权问题的,它起到的一个作用就是在你代码有疑问时,会提示你代码哪里错了,为什么错了。当然,你也可以理解为捕获了外部的变量的所有权。

move关键字的使用是相当简单的,只要在闭包前面加上move就可以了,会自动进行变量的捕获。示例代码如下所示

rust 复制代码
let v = vec![1, 2, 3];
    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });
    handle.join().unwrap();

二、mpsc多生产者单消费者模型

mpsc多个生产者,单个消费者(multiple producer, single consumer)的缩写。简而言之,Rust 标准库实现信道的方式意味着一个信道可以有多个产生值的 发送(sending)端,但只能有一个消费这些值的 接收(receiving)端。

1.创建一个简单的模型

mpsc是通过mpsc::channel来创建的,返回一个发送者(tx),一个接收者(rx)。

创建一个简单的模型代码如下,发送端在线程内部发送一个字符串Hello,然后接收端接收这个字符串。

rust 复制代码
let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let hello = String::from("Hello");
        tx.send(hello)
    });
    let received = rx.recv().unwrap();
    println!("接收到的数据是:{}", received)

代码运行效果如下

2.分批发送数据

在使用时,发送端可能会发送多次数据,或者是分批来发送数据,如果再使用

rust 复制代码
let received = rx.recv().unwrap();

来接收数据,则会起不到想要的效果,因为它只能接收一次,所以可以使用for循环来接收数据

rust 复制代码
for received in rx {
        println!("接收到的值: {}", received);
    }

这样只要是发送端发送的数据,接收端就都能接收到了,详细代码如下

rust 复制代码
    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_secs(1));
        }
    });
    // 不再显式调用 recv 函数
    for received in rx {
        println!("接收到的值: {}", received);
    }

3. 使用clone来产生多个生产者

rust 复制代码
let (tx, rx) = mpsc::channel();
    
    let tx1 = tx.clone();
    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];
    
        for val in vals {
            tx1.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
    
    thread::spawn(move || {
        let vals = vec![
            String::from("more"),
            String::from("messages"),
            String::from("for"),
            String::from("you"),
        ];
    
        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
    
    for received in rx {
        println!("Got: {}", received);
    }

三、共享状态:互斥锁

如果你对多线程开发有所了解,就一定了解过锁的概念。

1. 创建一个简单的锁

rust 复制代码
let m = Mutex::new(5);
    {
        // 使用 lock 方法获取锁,以访问互斥器中的数据。
        // 如果另一个线程拥有锁,并且那个线程 panic 了,则 lock 调用会失败。
        // MutexGuard也提供了一个 Drop 实现当离开作用域时自动释放锁
        let mut num = m.lock().unwrap();
        *num = 6;
    }
    println!("Mutex:{:?}", m);

2. 使用互斥锁解决引用问题

rust 复制代码
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());
相关推荐
2401_8532757317 分钟前
ArrayList 源码分析
java·开发语言
zyx没烦恼17 分钟前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
lb363636363618 分钟前
整数储存形式(c基础)
c语言·开发语言
feifeikon20 分钟前
Python Day5 进阶语法(列表表达式/三元/断言/with-as/异常捕获/字符串方法/lambda函数
开发语言·python
大鲤余27 分钟前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
浪里个浪的102429 分钟前
【C语言】从3x5矩阵计算前三行平均值并扩展到4x5矩阵
c语言·开发语言·矩阵
Source.Liu32 分钟前
不安全 Rust
安全·rust
她说彩礼65万35 分钟前
Asp.NET Core Mvc中一个视图怎么设置多个强数据类型
后端·asp.net·mvc
MoFe136 分钟前
【.net core】【sqlsugar】字符串拼接+内容去重
java·开发语言·.netcore
陈随易41 分钟前
农村程序员-关于小孩教育的思考
前端·后端·程序员