在Rust中使用多线程并发运行代码

news2024/11/26 2:46:27

1.Rust线程实现理念

在大部分现代操作系统中,已执行程序的代码在一个 进程process)中运行,操作系统则会负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这些运行这些独立部分的功能被称为 线程threads)。例如,web 服务器可以有多个线程以便可以同时响应多个请求。

将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:

  • 竞态条件(Race conditions),多个线程以不一致的顺序访问数据或资源。

  • 死锁(Deadlocks),两个线程相互等待对方,这会阻止两者继续运行。

  • 只会发生在特定情况且难以稳定重现和修复的 bug。

Rust 尝试减轻使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。

编程语言有一些不同的方法来实现线程,而且很多操作系统提供了创建新线程的 API。Rust 标准库使用 1:1 线程实现,这代表程序的每一个语言级线程使用一个系统线程。

2.使用spawn创建新线程

为了创建一个新线程,需要调用 thread::spawn 函数并传递一个闭包, 并在其中包含希望在新线程运行的代码。看下面的例子:

use std::thread;
use std::time::Duration;
​
fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
​
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

注意当 Rust 程序的主线程结束时,新线程也会结束,而不管其是否执行完毕。这个程序的输出可能每次都略有不同,不过它大体上看起来像这样:

thread::sleep 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头,甚至即便我们告诉新建的线程打印直到 i 等于 9,它在主线程结束之前也只打印到了 5。

如果运行代码只看到了主线程的输出,或没有出现重叠打印的现象,尝试增大区间 (变量 i 的范围) 来增加操作系统切换线程的机会。

3.使用join等待所有线程结束

由于主线程结束,上面演示的代码大部分时候不光会提早结束新建线程,因为无法保证线程运行的顺序,甚至不能实际保证新建线程会被执行!

可以通过将 thread::spawn 的返回值储存在变量中来修复新建线程部分没有执行或者完全没有执行的问题。thread::spawn 的返回值类型是 JoinHandleJoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。

看下面的示例代码:

use std::thread;
use std::time::Duration;
​
fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
​
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
​
    handle.join().unwrap();
}

通过调用 handle 的 join 会阻塞当前线程直到 handle 所代表的线程结束。阻塞Blocking)线程意味着阻止该线程执行工作或退出。因为我们将 join 调用放在了主线程的 for 循环之后,编译这段代码后运行结果如下:

这两个线程仍然会交替执行,不过主线程会由于 handle.join() 调用会等待直到新建线程执行完毕。

不过如果将 handle.join() 移动到 mainfor 循环之前会发生什么呢,看下面的代码:

use std::thread;
use std::time::Duration;
​
fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
​
    handle.join().unwrap();
​
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

代码编译执行后结果如下:

主线程会等待直到新建线程执行完毕之后才开始执行 for 循环,所以输出将不会交替出现。

因此,将join放在代码的不同地方, 将会影响线程是否同时执行。

4.将move闭包与线程一起使用

move 关键字经常用于传递给 thread::spawn 的闭包,因为闭包会获取从环境中取得的值的所有权,因此会将这些值的所有权从一个线程传送到另一个线程。

为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值, 下面的代码展示了一个尝试在主线程中创建一个 vector 并用于新建线程的例子,不过这么写还不能工作, 代码如下:

use std::thread;
​
fn main() {
    let v = vec![1, 2, 3];
​
    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });
​
    handle.join().unwrap();
}

闭包使用了 v,所以闭包会捕获 v 并使其成为闭包环境的一部分。因为 thread::spawn 在一个新线程中运行这个闭包,所以可以在新线程中访问 v。然而当编译这个例子时,会得到如下错误:

Rust 会 推断 如何捕获 v,因为 println! 只需要 v 的引用,闭包尝试借用 v。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓对 v 的引用是否一直有效。

看一段比较极端情况的代码:

use std::thread;
​
fn main() {
    let v = vec![1, 2, 3];
​
    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });
​
    drop(v); // 坏事儿了!
​
    handle.join().unwrap();
}

如果 Rust 允许这段代码运行,则新建线程则可能会立刻被转移到后台并完全没有机会运行。新建线程内部有一个 v 的引用,不过主线程立刻就使用drop丢弃了v。接着当新建线程开始执行,v 已不再有效,所以其引用也是无效的。

为了修复上面的编译错误, 我们可以根据编译器给予我们的help尝试修正一下,如图:

通过在闭包之前增加 move 关键字,强制闭包获取其使用的值的所有权,而不是任由 Rust 推断它应该借用值。

修正后的代码如下:

use std::thread;
​
fn main() {
    let v = vec![1, 2, 3];
​
    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });
​
    handle.join().unwrap();
}

编译运行试一下:

看起来没问题,那么以这个成功的经验, 修改那段极端情况的代码如下:

use std::thread;
​
fn main() {
    let v = vec![1, 2, 3];
​
    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });
​
    drop(v); // 坏事儿了!
​
    handle.join().unwrap();
}

再次编译一下看看结果如何:

Rust编译器依然没有放行, 这个修复行不通。

如果为闭包增加 move,将会把 v 移动进闭包的环境中, 因此将不能在主线程中对其调用 drop 了, Rust 的所有权规则又一次帮助我们杜绝了隐患。因为 Rust 是保守的并只会为线程借用 v,这意味着主线程理论上可能使新建线程的引用无效。通过告诉 Rust 将 v 的所有权移动到新建线程,我们向 Rust 保证主线程不会再使用 v。如果对其作出同样的move修改, 那么当在主线程中使用 v 时就会违反所有权规则。 move 关键字覆盖了 Rust 默认保守的借用,但它不允许我们违反所有权规则。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【js逆向实战】某sakura动漫视频逆向

写在前面 再写一个逆向实战,后面写点爬虫程序来实现一下。 网站简介与逆向目标 经典的一个视频网站,大多数视频网站走的是M3U8协议,就是一个分段传输,其实这里就有两个分支。 通过传统的m3u8协议,我们可以直接进行分…

千万别对女项目经理抱有幻想

大家好,我是老原。 前段时间,有一个粉丝朋友来咨询我一个问题,多少是有点把老原我问蒙圈 现在职场上女PM并不少见,也有很多优秀的女PM。 我也有不少和女PM合作的经历,不得不说,和她们沟通/合作可以说是很…

详解卷积神经网络结构

前言 卷积神经网络是以卷积层为主的深度网路结构,网络结构包括有卷积层、激活层、BN层、池化层、FC层、损失层等。卷积操作是对图像和滤波矩阵做内积(元素相乘再求和)的操作。 1. 卷积层 常见的卷积操作如下: 卷积操作解释图解…

能谈一下 CAS 机制吗

(本文摘自mic老师面试文档) 一个小伙伴私信我,他说遇到了一个关于 CAS 机制的问题,他以为面试官问的是 CAS 实现单点登录。 心想,这个问题我熟啊,然后就按照单点登录的思路去回答,结果面试官一…

Azure - 机器学习:自动化机器学习中计算机视觉任务的超参数

Azure Machine Learning借助对计算机视觉任务的支持,可以控制模型算法和扫描超参数。 这些模型算法和超参数将作为参数空间传入以进行扫描。 关注TechLead,分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验,同济…

美国Embarcadero公司正式发布2023 RAD Studio Delphi C++ Builder 12 Athens

Embarcadero 非常高兴地宣布发布 RAD Studio 12 Athens 以及 Delphi 12 和 CBuilder 12。RAD Studio 12 Athens 版本包含令人兴奋的新功能,为该产品的未来奠定了基础。 目录 主要新功能 C 的奇妙之处Delphi 的一些不错的补充FireMonkey 和 Skia 作为新基金会采用 MD…

设计模式(3)-结构型模式

结构型模式 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则…

从浏览器输入一个URL到最终展示出页面,中间会发送哪些事儿?

文章目录 前言一. DNS域名解析二. 进行封装三. 进行传输四. 到达服务器后层层分用五. 服务器把响应数据重新封装六. 响应数据进行传输七. 到达客户端层层分用八. 将网页渲染到浏览器上 前言 当你输入一个网址:www.baidu.com时,浏览器究竟做了哪些工作才…

C++ Qt 学习(四):自定义控件与 qss 应用

1. qss 简介 Qt style sheet(qss,Qt 样式表),不需要用 C 代码控件进行重载,就可以修改控件外观,类似于前端的 css 2. qss 选择器 2.1 通配符选择器 /* 设置后控件窗口背景色都被修改为黄色 */ * {backg…

如何在Android平板上远程连接Ubuntu服务器code-server进行代码开发?

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机,Ubuntu或者centos都可以,这里以VMwhere ubuntu系统为例 下载code serve…

Vue3使用vue-print-nb插件打印功能

插件官网地址https://www.npmjs.com/package/vue-print-nb 效果展示: 打印效果 根据不同的Vue版本安装插件 //Vue2.0版本安装方法 npm install vue-print-nb --save pnpm install vue-print-nb --save yarn add vue-print-nb//Vue3.0版本安装方法: npm install vue3…

CentOS Linux 系统镜像

CentOS Linux具有以下特点: 稳定性:CentOS Linux旨在提供一个稳定、可靠的服务器环境,适合用于关键业务应用和生产环境。高效性:CentOS Linux经过优化和调整,可以充分发挥硬件的性能,提高系统的整体效率。…

2023 年最新腾讯官方 QQ 机器人(QQ 群机器人 / QQ 频道机器人)超详细开发教程

注册 QQ 开放平台账号 QQ 开放平台是腾讯应用综合开放类平台,包含 QQ 机器人、QQ 小程序、QQ 小游戏 等集成化管理,也就是说你注册了QQ 开放平台,你开发 QQ 机器人还是 QQ 小程序都是在这个平台进行部署上线和管理。 如何注册 QQ 开放平台账…

element ui:常用的组件使用情况记录

前言 将element ui使用过程中一些常用的组件使用情况记录如下 组件 el-tree树组件 树父子节点成一列显示 没有进行设置之前显示效果 设置之后显示效果 ​​​​ 主要代码如下 <el-treeicon-class"none"expand-on-click-node"false"style"…

【uniapp/uview】Collapse 折叠面板更改右侧小箭头图标

最终效果是这样的&#xff1a; 官方没有给出相关配置项&#xff0c;后来发现小箭头不是 uview 的图标&#xff0c;而是 unicode 编码&#xff0c;具体代码&#xff1a; // 箭头图标 ::v-deep .uicon-arrow-down[data-v-6e20bb40]:before {content: \1f783; }附一个查询其他 u…

HTML+CSS、Vue+less+、HTML+less 组件封装实现二级菜单切换样式跑(含全部代码)

一、HTMLCSS二级菜单 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</title><…

蓝桥杯国一,非ACMer选手保姆级经验分享

目录 一、前言二、蓝桥杯简介三、0基础计算机新手小白&#xff0c;赛前如何准备提高自己的获奖率&#xff1f;3.1 每两周参加一次【蓝桥算法双周赛】3.2 多练真题3.3 参加每一场官方校内模拟赛 四、结语 一、前言 hello&#xff0c;大家好&#xff0c;我是大赛哥(弟)&#xff…

2023.11-9 hive数据仓库,概念,架构

目录 一.HDFS、HBase、Hive的区别 二.大数据相关软件 三. Hive 的优缺点 1&#xff09;优点 2&#xff09;缺点 四. Hive 和数据库比较 1&#xff09;查询语言 2&#xff09;数据更新 3&#xff09;执行延迟 4&#xff09;数据规模 五.hive架构流程 六.MetaStore元…

Could not load library libcudnn_cnn_train.so.8, 解决类似问题的思路与方法

完整报错 Could not load library libcudnn_cnn_train.so.8. Error: /home/ai/anaconda3/envs/ai/bin/../lib/libcudnn_ops_train.so.8: undefined symbol: _ZN5cudnn3ops26JoinInternalPriorityStreamEP12cudnnContexti, version libcudnn_ops_infer.so.8 错误原因 该错误其…

Tcl语言:基础入门(一)

Tcl语言https://blog.csdn.net/weixin_45791458/category_12488978.html?spm1001.2014.3001.5482 Tcl语言是一种脚本语言&#xff0c;类似于Bourne shell(sh)、C shell&#xff08;csh&#xff09;、Bourne-Again Shell(bash)等UNIX shell语言。Shell程序主要作为胶水缝合其他…