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

文章目录

  • 前言
  • 一、创建线程
  • 二、mpsc多生产者单消费者模型
    • 1.创建一个简单的模型
    • 2.分批发送数据
    • 3. 使用clone来产生多个生产者
  • 三、共享状态:互斥锁
    • 1. 创建一个简单的锁
    • 2. 使用互斥锁解决引用问题


前言

并发编程(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支持更加精确的时间,你可以去看一下这个类,支持微秒纳秒等。

    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 放在循环之前可以实现先执行子线程【这种操作会变得同步,影响程序运行,因此需要方队地方,仔细斟酌】

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

    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就可以了,会自动进行变量的捕获。示例代码如下所示

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,然后接收端接收这个字符串。

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

代码运行效果如下

2.分批发送数据

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

let received = rx.recv().unwrap();

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

for received in rx {
        println!("接收到的值: {}", received);
    }

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

    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来产生多个生产者

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. 创建一个简单的锁

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

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

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());

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/476291.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络: 传输层

功能: 将数据从发送到传给接收端 UDP 无连接状态: 知道对端的IP和端口号就直接进行传输, 不需要建立连接不可靠: 没有确认机制, 没有重传机制. 出错不会管面向数据包: 不能够灵活的控制读写数据的次数和数量 发送速度快: 立即发送 报文结构 TCP 面向连接可靠 校验和序列号(按…

Java项目基于Docker打包发布

1.打包应用 mvn clean package -DskipTests 或者 2.新建dockerfile FROM openjdk:8 #设置工作目录 WORKDIR /opt#COPY wms-app-0.0.1-SNAPSHOT.jar /wms-app/app.jar ADD wms-app-0.0.1-SNAPSHOT.jar app.jar #配置容器暴露的端口 EXPOSE 8080 #查看是否已经copy进去 R…

YOLOv1学习

YOLO系列学习笔记 YOLOv1评价指标PrecisionRecallAPmAP 置信度分数统一检测框架网络结构训练损失函数 测试YOLOv1的不足实验结论 YOLOv1 优点: 快全图推理,背景错误率低泛化能力强 每个图像固定大小 448*448,系统将输入图像分成S S网格。…

视频素材库哪里找?推荐几个高质量的无水印视频素材网

在寻找创意优质素材的道路上,拥有一个好的导航仪至关重要。这不仅仅是关于找到一张图片或一个视频,而是关于发现那些能让你的项目闪耀的宝藏。今天,我将混合介绍国内外的素材网站,旨在为你提供一个全面的视角,同时尽量…

Python之Web开发中级教程----Django站点管理

Python之Web开发中级教程----Django站点管理 网站的开发分为两部分:内容发布和公共访问 内容发布是由网站的管理员负责查看、添加、修改、删除数据 Django能够根据定义的模型类自动地生成管理模块 使用Django的管理模块, 需要按照如下步骤操作 : 1.管理界面本地…

Python 安装目录及虚拟环境详解

Python 安装目录 原文链接:https://blog.csdn.net/xhyue_0209/article/details/106661191 Python 虚拟环境 python 虚拟环境图解 python 虚拟环境配置与详情 原文链接:https://www.cnblogs.com/hhaostudy/p/17321646.html

C++进阶02 多态性

听课笔记简单整理,供小伙伴们参考~🥝🥝 第1版:听课的记录代码~🧩🧩 编辑:梅头脑🌸 审核:文心一言 目录 🐳课程来源 🐳前言 🐋运…

LeetCode困难题----84.柱状图中的最大矩形

今天刷LeetCode时遇到了一个很有意思的题: 看了半天题解还是没理解他的代码想要表达的是什么意思,在思考了很久之后,终于,我理解了这道题,接下来让我带你们走进这道题。 这道题的大概意思是,给你一个heights[]数组,(宽为1)让你求出他们可以组合出的最大面积 首先,我们先用暴力法…

MQTTnet实现客户端连接

使用MQTTnet(Version=4.3.1.873)库实现多客户端连接多服务端,同时实现断线重连; 如下图所示,开启3个客户端连接3个服务端,当其一个服务端出现异常(服务停止,网络异常无法连接)导致连接断开时,实现每5秒连接一次 MQTT连接服务核心类:业务需求是一个客户端对应的一个MQ…

libVLC 设置视频宽高比

宽高比是指视频图像的宽度和高度之间的比率。 投影屏幕尺寸一般都按照对角线的大小来定义的。根据图像制式不同,屏幕的长宽比例也有几种格式: 传统影视的宽高比是 4:3,宽屏幕电影的宽高比是 1.85:1,高清晰…

python中获取当前项目的目录

大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂 今天介绍一下,如何在python中获取当前项目所在的目录,而不是运行脚本的目录。 class ProjectPaths:# 初始化时获取当前脚本的路径staticmethoddef get_script_dir():…

C# 设置AutoScroll为true没效果的原因分析和解决办法

C#中添加tabControl 分页,将autoscroll设置为true发现缩小窗口没有滚动条效果。该问题出现后,检索发现也有很多人询问了该问题,但是都没有给出解决方案。 原因是内部button的属性Anchor设置为top、left、right、bottom导致的缩小界面窗口也没…

算法:一些DFS的经验

DFS:可以看作是向下遍历树的模拟 剪枝:减少时间复杂度 一个dfs所需要具备的元素: 一,出口 1.出口:每一个进入的dfs的出口,可以是枚举全部元素后退出该dfs,也可以是大于层数或剪枝条件........ 二,向下搜…

操作符(C语言)—第二期

赋值操作符 赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。 int weight 120;//体重 weight 89;//不满意就赋值 double salary 10000.0; salary 20000.0;//使用赋值操作符赋值。赋值操作符可以连续使用&#x…

Design Script官方案例解析2:程序简写

在本练习中,我们将调整新的简写技能,以创建由范围和公式定义的精美蛋壳曲面。在本练习中,请注意我们如何串联使用代码块和现有 Dynamo 节点:我们将代码块用于繁重的数据提升,而 Dynamo 节点以可视方式布局来使定义清晰易读。 首先,通过连接上述节点创建曲面。请勿使用数字…

关于VMware Workstation Pro无法与Windows互相进行复制粘贴的解决方案

说明:要实现Windows在wmware虚拟机上实现复制粘贴需要在虚拟机上下载 VMware Tools 工具。 1.查看虚拟机是否下载了VMware Tools工具。(下载了vMware Tools 会变成灰色的) 2.要是成功安装的话,你在去改一下这里。 设置完到这里理…

Redis缓存穿透的几种解决方案

目录 缓存穿透原理: 缓存穿透一般有几种解决方案: 1.缓存空值 2.使用锁 3.布隆过滤器 优缺点 布隆过滤器误判理解 布隆过滤器的简单使用流程 4.组合方案 那么当我们高并发的访问短链接或者人为的去穿透的时候呢? 最近做项目遇到了缓…

多聆听,少评判

当朋友来找你倾诉、吐槽、诉苦,或是表达情绪的时候,你是怎样回应的? 许多人总有这样的习惯:每当听到朋友的倾诉,或者在网上看到别人诉苦时,第一反应往往是提建议:为什么你不试试这样做呢&#x…

代码随想录算法训练营第二十九天|491. 非递减子序列,46.全排列

491. 非递减子序列 题目 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种…

Kubernetes中PV和PVC的几种状态类型

文章目录 1、PV和PVC概念1.1、PV1.2、PVC 2、PV / PVC的关系3、PV / PVC的状态类型3.1. Available(可用)3.2. Bound(已绑定)3.3. Released(已释放)3.4. Pending(待定)3.5. Failed&am…