Rust模块std::thread

news2024/11/15 21:09:47

【图书介绍】《Rust编程与项目实战》-CSDN博客

《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Rust到底值不值得学,之一  -CSDN博客

Rust到底值不值得学,之二-CSDN博客

Rust多线程编程概述-CSDN博客

12.3.2  等待所有线程完成

在前面的实例中,主线程没等到派生线程执行完毕就结束了,从而整个进程就会结束。那么怎么让派生线程执行完毕呢?答案是通过joinHandle结构体来等待所有线程完成。要了解派生线程何时完成,有必要捕获thread::spawn函数返回的JoinHandle,该结构体声明如下:

pub struct JoinHandle<T>(_);

该结构体通常由thread::spawn函数返回,或者由thread::Builder::spawn函数返回。JoinHandle在关联线程被丢弃时分离该线程,这意味着该线程不再有任何句柄,也无法对其进行连接。由于平台限制,无法克隆此句柄:加入线程的能力是唯一拥有的权限。

该结构体提供了一个函数join,允许调用方(比如主线程)等待派生线程(比如子线程)完成,该函数声明如下:

pub fn join(self) -> Result<T>

该函数等待相关线程完成。如果相关线程已经完成,则函数将立即返回。就原子内存排序而言,相关线程的完成与此函数返回同步。换句话说,该线程执行的所有操作都发生在 join 返回之后发生的所有操作之前。join的返回值通常是子线程执行的结果。

join函数的用法如下:

use std::thread;



let thread_join_handle = thread::spawn(|| {

    //子线程执行的代码

});

//主线程执行的代码

let res = thread_join_handle.join();      //等待子线程结束

thread_join_handle存放joinHandle结构,然后调用join方法,可以等待对应的线程执行完成。调用handle的join方法会阻止当前运行线程的执行,直到handle所表示的这些线程终结join方法返回一个线程结果值,如果线程崩溃,则返回错误码,否则返回Ok。

res将得到子线程执行的结果,我们甚至可以在创建线程时,在子线程执行的代码处直接放一个数值或字符串,从而让res得到这个数值或字符串。

如果希望join调用失败时报错一下,可以这样:

thread_join_handle.join().expect("Couldn't join on the associated thread"); //若有问题就会有提示

下面先看一个简单的实例,得到子线程的结果,相当于实现了子线程传递一个值给主线程。

【例12.2】  子线程传递值给主线程

   打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。在main.rs中,添加代码如下:

use std::thread;

fn main() {
    let other_thread = thread::spawn(|| {
         "hello" 		 //这里就写了一个字符串,相当于子线程的执行结果就是字符串"hello"
    });
    let  res = other_thread.join().unwrap();		 //得到子线程执行结果,即"hello"
    println!("{}",res); 
}

保存文件并运行,运行结果如下:

hello

如果有兴趣,还可以把"hello"改为一个整数,那么res就得到这个整数值。我们甚至可以把一个函数返回值作为子线程结果传递给主线程,下面来看一个实例。

【例12.3】  把函数返回值传递给主线程

   打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。在main.rs中,添加代码如下:

use std::thread;
fn thfunc(n: u32) -> u32 { 			//这个函数是线程函数,后面会讲到
    return n+1;
}
fn main() {
    let child = thread::spawn(|| {
        let f = thfunc(30); 			//调用线程函数
        f   							//返回子线程结果,这里也就是函数thfunc的返回值
    });

    let res = child.join().expect("Could not join child thread");
    println!("{}",res);
}

函数thfunc把参数n加1后再返回,并存于f中,然后把f作为子线程的结果,这样主线程通过join函数就可以得到f的值,也就是函数thfunc的返回值。

 保存文件并运行,运行结果如下:

31

下面再看一个稍复杂点的实例,加一些循环打印。

【例12.4】  等待子线程执行完毕

  打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。

 在main.rs中,添加代码如下:

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {  		//返回一个 JoinHandle 类型的值
        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(); 		//阻止当前线程(主线程)执行,并等待子线程执行完毕
}

thread::spawn返回一个JoinHandle类型的值,可以将它存放到变量中。这个类型相当于子线程的句柄,用于连接线程。如果忽略它,就没有办法等待线程。在主线程main函数的结尾,我们调用了join方法来等待子线程执行完毕,即调用handle的join方法会阻止当前运行线程的执行,直到handle所表示的这些线程终结。unwrap 是一个方法,它用于从Option或Result类型中提取值。

  保存文件并运行,运行结果如下:

hi number 1 from the main thread!
hi number 2 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the spawned thread!
hi number 3 from the main thread!
hi number 4 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!

可以看到,子线程中的for循环全部执行完毕了。

不和主线程交互的子线程是非常少见的。Rust语言和其他语言不一样的地方是,如果线程中直接使用了其他线程定义的数据,则会报错。这里所说的外部变量就是其他线程中定义的变量。比如下面的代码,子线程中直接使用主线程定义的字符串就会报错:

use std::thread;

fn main() {
    let data = String::from("hello world");
    let thread = std::thread::spawn(||{
        println!("{}", data);
    });
    thread.join();
}

如果编译就会报错:

error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function

线程中使用了其他线程的变量是不合法的,必须使用move表明线程拥有data的所有权,我们可以使用move关键字把data的所有权转到子线程内,代码如下:

use std::thread;
fn main() {
    let data = String::from("hello world");
    let thread = std::thread::spawn(move ||{	//使用move 把data的所有权转到线程内
        println!("{}", data);
    });
    thread.join();
}

这个时候,就能正确输出结果“hello world”了。

move闭包通常和thread::spawn函数一起使用,它允许用户使用其他线程的数据,这样在创建新线程时,可以把其他线程中的变量的所有权从一个线程转移到另一个线程,然后就可以使用该变量了。下面来看一个实例,一个常见的应用模式是使用多线程访问列表中的元素来执行某些运算。

【例12.5】  多个子线程使用主线程中的数据

  打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。

  在main.rs中,添加代码如下:

use std::thread;

fn main() {
    let v = vec![1, 3, 5, 7, 9];
    let mut childs = vec![];
    for n in v {
        let c = thread::spawn(move || {
            println!("{}", n * n);
        });
        childs.push(c);
    };

    for c in childs {   				//等待所有子线程结束
        c.join().unwrap();
    }
}

这里的move是必要的,否则没法保证主线程中的v会不会在子线程结束之前被销毁。使用move之后,所有权转移到了子线程内,从而使得不会出现因为生命周期造成的数据无效的情况。子线程的执行周期可能比主线程还长。因此,很可能出现结果还没有完全打印出来,就已经结束的情况。为了防止这个情况,我们存下每个线程句柄,并在最后使用 join 阻塞主线程。

  保存文件并运行,运行结果如下:

1
9
25
49
81

12.3.4  线程函数

现在我们知道了,thread::spawn 函数接受一个闭包作为参数,闭包中的代码会在子线程中执行,比如:

let handle = thread::spawn(|| {

    //子线程执行的代码

});

但如果子线程执行的代码比较长,我们通过会另外写一个函数来封装这些代码,这样在thread::spawn中只需写一个函数调用即可,这个在新线程中执行的函数通常称为线程函数。下面来看一个实例,我们设计了一个线程函数,并且它是一个递归函数,为了模拟长时间运行。

【例12.6】  使用一个递归的线程函数

  打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。

  在main.rs中,添加代码如下:

use std::thread;

fn fibonacci(n: u32) -> u32 { 	//这个函数是线程函数,并且是一个递归函数,用于求斐波那契数列
    if n == 0 {
        return 0;
    } else if n == 1 {
        return 1;
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}
fn main() {
    let child = thread::spawn(|| {
        let f = fibonacci(30); 							//调用线程函数
        println!("Hello from a thread!. fibonacci(30) = {}", f);  	//打印数列结果
        f   						//返回子线程结果,这里也就是函数fibonacci的返回值
    });

    println!("Hello, world!"); 	//主线程中执行的代码
    let v = child.join().expect("Could not join child thread"); //等待子线程结束,并得到子线程结果
    println!("value: {:?}", v); 
}

我们把子线程中要执行的代码(这里是求斐波那契数列)单独放在一个函数fibonacci中,这样在thread::spawn函数中只需要调用该函数(fibonacci(30);)即可,这样代码简洁多了,而且方便模块化开发,比如可以让算法工程师专门实现斐波那契数列函数,而其他程序员只需要调用即可,这样可以做到并行开发,提高了效率。斐波那契数列函数执行时间较长,主线程末尾一定要等待子线程结束,也就是调用join函数,join返回一个Result,可以使用 expect 方法来获取返回值。这样主线程就会等待子线程完成,而不会先结束程序,这样就可以看到我们想要的结果。

  保存文件并运行,运行结果如下:

Hello, world!

Hello from a thread!. fibonacci(30) = 832040

value: 832040

12.3.5  available_parallelism返回默认并行度

available_parallelism函数返回程序应使用的默认并行度的估计值。该函数声明如下:

pub fn available_parallelism() -> Result<NonZeroUsize>

并行性是一种资源,一台给定的机器提供了一定的并行能力,即它可以同时执行的计算数量的限制。这个数字通常对应CPU或计算机的数量,但在各种情况下可能会有所不同。诸如VM或容器编排器之类的主机环境可能希望限制其中的程序可用的并行量。这样做通常是为了限制(无意中)resource-intensive程序对同一台机器上运行的其他程序的潜在影响。

提供此函数的目的是提供一种简单且可移植的方式来查询程序应使用的默认并行度。它不公开有关NUMA区域的信息,不考虑(协)处理器能力的差异,并且不会修改程序的全局状态以更准确地查询可用并行度的数量。资源限制可以在程序运行期间更改,因此不会缓存该值,而是在每次调用此函数时重新计算,不应从热代码中调用它。

此函数返回的值应被视为在任何给定时间可用的实际并行量的简化近似值。要更详细或更准确地了解程序可用的并行量,用户可能还希望使用特定平台的API。以下平台限制当前适用于available_parallelism。

(1)在Windows上:它可能低估了具有超过64个逻辑CPU的系统上可用的并行量。但是,程序通常需要特定的支持才能利用超过64个逻辑CPU,并且在没有此类支持的情况下,此函数返回的数字准确地反映了程序默认可以使用的逻辑CPU的数量。它可能会高估受process-wide关联掩码或作业对象限制的系统上可用的并行量。

(2)在Linux上:当受process-wide关联掩码限制或受cgroup限制影响时,它可能会高估可用的并行量。

在具有CPU使用限制的VM(例如过度使用的主机)中运行时,可能会高估可用的并行量。此函数将在以下情况下(但不限于)返回错误:如果目标平台的并行量未知,或者程序没有权限查询可用的并行量。

【例12.7】  得到当前系统的默认并行度

   打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。

  在main.rs中,添加代码如下:

use std::{io, thread};



fn main() -> io::Result<()> {

    let count = thread::available_parallelism()?.get();

    assert!(count >= 1_usize);

    println!("{},{}",count,1_usize);

    Ok(())

}

1_usize的值就是1。我们把得到的默认并行度存于count中,如果count小于1,那就抛出异常,否则打印结果。

  保存文件并运行,运行结果如下:

2,1

可见,当前系统的默认并行度是2。

当前线程的属性包括线程id、名称。获取它们的函数被定义在std::thread:: Thread这个结构体中,所以我们首先要获取这个结构体,也就是获取当前线程的Thread结构体,这个函数是current,声明如下:

pub fn current() -> Thread

Thread其实是std::thread的一个私有结构(Struct),这个结构体对外提供了一些函数,以此来获取线程的id、名称等属性。

有了当前线程的Thread结构,就可以得到名称,该函数声明如下:

pub fn name(&self) -> Option<&str>

该函数返回字符串。

【例12.8】  获取和设置线程名称

   打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。在main.rs中,添加代码如下:

use std::thread;

​​​​​​​fn main() {

    let thr0 = thread::current();                      //获取当前线程的Thread结构

    let thread_name = thr0.name().unwrap_or("unknown");    //获取当前线程的名称

    println!("当前线程的名称:{}", thread_name);             //打印输出名称

}

unwrap_or是用于从Result对象中获取值的宏。当Result对象是Ok时,两者都会返回Ok中的值。但是当Result对象是Err时,unwrap_or将返回一个默认值。这个默认值是宏的参数,在调用unwrap_or时就已经确定了。所以,当你想要在Result对象是Err时使用固定的默认值时,就可以使用unwrap_or。

  保存文件并运行,运行结果如下:

当前线程的名称:main

下面再看获得当前线程的id,线程id用于区分不同的线程,id号是唯一的。在Rust中,获得当前线程id的函数声明如下:

pub fn id(&self) -> ThreadId

该函数的返回值是一个结构体ThreadId,该结构体的大小是8字节。ThreadId是一个不透明的对象,它唯一地标识在进程生存期内创建的每个线程。线程ID保证不会被重用,即使在线程终止时也是如此。ThreadId受Rust的标准库控制,ThreadId和底层平台的线程标识符概念之间可能没有任何关系。因此,这两个概念不能互换使用。ThreadId可以从结构体Thread上的id函数中得到结果。

【例12.9】  得到线程id

  打开VS Code,单击菜单Terminal→New Termanal,执行命令cargo new myrust来新建一个Rust工程,工程名是myrust。在main.rs中,添加代码如下:

use std::thread;



fn main() {

    let child_thread = thread::spawn(|| {          //创建线程

         thread::current().id()        //在子线程中执行的代码得到的就是当前子线程的id

    });             

    let child_thread_id = child_thread.join().unwrap();//得到子线程的id

    assert!(thread::current().id() != child_thread_id);//如果表达式为假,则触发断言

    println!("thread::current().id()={:?},child_thread_id={:?}", thread::current().id(),child_thread_id);

}

在代码中,我们不仅得到了当前线程id,还创建了另一个线程,并且比较了这两个线程id。assert!宏用于检查一个表达式是否为真(true),如果表达式为假(false),则会触发断言,程序会终止运行。

  保存文件并运行,运行结果如下:

thread::current().id()=ThreadId(1),child_thread_id=ThreadId(2)

可见,主线程和子线程的id不一样。

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

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

相关文章

合碳智能 × Milvus:探索化学合成新境界——逆合成路线设计

合碳智能&#xff08;C12.ai&#xff09;成立于2022年&#xff0c;致力于运用AI和具身智能技术&#xff0c;为药物研发实验室提供新一代智能化解决方案&#xff0c;推动实验室从自动化迈向智能化&#xff0c;突破传统实验模式与人员的依赖&#xff0c;解决效率和成本的瓶颈&…

1. GIS开发工程师岗位职责、技术要求和常见面试题

本系列文章目录&#xff1a; 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…

Leetcode每日刷题之76.最小覆盖子串(C++)

1.题目解析 本题的题目是给定两个字符串 s 和 t &#xff0c;找出在 s 中的某个最小子串保证该子串中包含所以 t 中出现的字母即可&#xff0c;并且该结果是唯一答案&#xff0c;找不到结果就直接返回空串即可 2.算法原理 关于本题的核心思路就是"滑动窗口"&#xff…

【Python 千题 —— 算法篇】首字母大写

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目背景 在文本格式化和处理过程中,常常需要将字符串的首字母大写。这在各种场景中都有实际应用,例如在标题格式化、用户输入校验、生成显示友好的文本等场景中。…

CC6链漏洞

CC6链漏洞 一 cc链简介 CC链是Apache Commons Collections反序列化漏洞利用链的简称&#xff0c;它涉及将可以执行命令的函数&#xff08;如Runtime.getRuntime().exec("calc.exe")&#xff09;序列化为对象流并转化为文件流存储在文件中&#xff0c;然后通过反序列…

深度学习5从0到1理解RNN(包括LTSM,GRU等):内容丰富(上)

循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09; 是一种经典的深度学习网络结构&#xff0c;具有广泛的应用。其中&#xff0c;槽填充&#xff08;Slot Filling&#xff09;&#xff08;即识别自然语言中的特定信息&#xff09; 是其中一个应用场景&#x…

香橙派开启vnc

1连接香橙派 2. 更新系统 在SSH会话中&#xff0c;首先更新系统软件包列表并升级现有软件包&#xff1a; sudo apt update sudo apt upgrade3. 安装VNC服务器 安装VNC服务器软件&#xff0c;这里以x11vnc为例&#xff1a; sudo apt install x11vnc 出现如图输入如下代码即可…

Python爬虫:通过js逆向获取某瓜视频的下载链接

爬虫:通过js逆向获取某瓜视频的下载链接 1. 前言2. 获取script标签下的视频加密数据3. 第一步:获取解密后的视频下载链接4. 第二步:模拟生成加密的webid值 1. 前言 就小编了解&#xff0c;某瓜视频这个网站对应视频下载链接加密处理至少经过三个版本。之前在CSDN发布了一篇关于…

船舶机械设备5G智能工厂物联数字孪生平台,推进制造业数字化转型

船舶机械设备5G智能工厂物联数字孪生平台&#xff0c;推进制造业数字化转型。在当今数字化浪潮推动下&#xff0c;船舶制造业正经历着前所未有的变革。为了应对市场的快速变化&#xff0c;提升生产效率&#xff0c;降低成本&#xff0c;并增强国际竞争力&#xff0c;船舶机械设…

Docker 详解及详细配置讲解

Docker 简介 2008 年LXC(LinuX Contiainer)发布&#xff0c;但是没有行业标准&#xff0c;兼容性非常差 docker2013年首次发布&#xff0c;由Docker, Inc开发 什么是 Docker Docker是管理容器的引擎&#xff0c;为应用打包、部署平台&#xff0c;而非单纯的虚拟化技术&#xf…

【springboot】使用swagger生成接口文档

1. 添加依赖 <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.6.0</version></dependency> 这里我老是添加不上这个依赖&#xff0c;搜索了下发现阿里…

《2024网络安全十大创新方向》

网络安全是创新驱动型产业&#xff0c;技术创新可以有效应对新的网络安全挑战&#xff1b;或是通过技术创新降低人力成本投入&#xff0c;提升企业运营效率。为推动行业技术创新、产品创新与应用创新&#xff0c;数说安全发布《2024年中国网络安全十大创新方向》&#xff0c;涵…

K8s高可用集群部署----超详细(Detailed Deployment of k8s High Availability Cluster)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

sM4040B科学级显微制冷相机特性

sM4040B科学级显微制冷相机特性 sM4040B搭载了 GSENSE4040BSI 3.2 英寸图像传感器&#xff0c;针对传感器固有的热噪声&#xff0c;专门设计了高效制冷模块&#xff0c;使得相机传感器的工作温度比环境温度低达 35-40 度。针对制冷相机常见的低温结雾现象设计了防结雾机制&…

【图灵完备 Turing Complete】游戏经验攻略分享 Part.3 存储器

这一章&#xff0c;前面不难&#xff0c;后面难。 教你别这么连线连出问题。 看结果说话&#xff0c;延迟两个时刻输出。 先不管要求&#xff0c;输出一个稳定的信号&#xff0c;看看之前给了延迟元件正好延迟一刻&#xff0c;然后作为输入和那个稳定的信号做一个逻辑运算改变…

逻辑导论前传

人类的逻辑运算建立在已有的数据库上&#xff0c; 我们无法处理逻辑问题&#xff0c;是因为宇宙的意志不允许我们得出正确答案&#xff0c;每个人都是一个答案&#xff0c;当你认知到了所有人&#xff0c;你也就得到了所有正确答案&#xff0c;这时候宇宙智慧采取正确答案的逻辑…

绿色无广告,纯净体验——2024年优质免费视频剪辑软件

如果你习惯一个视频网站的时候&#xff0c;工作上遇到问题也会第一时间在视频网站上进行搜索解决方案。就比如我同事就很喜欢在短视频网站上搜索Office软件的一些操作步骤。如果你也想分享这类视频&#xff0c;那么我们一起探讨下有哪些适合抖音剪辑的视频剪辑工具。 1.福昕视…

Linux下安装Docker-ce ,配置nginx容器

引言 直接在windows系统中使用nginx服务&#xff0c;面临着如下问题&#xff1a; 1.性能瓶颈 高并发处理能力有限&#xff0c;资源利用率不高。 2.兼容性和稳定性问题 Nginx最初是为Linux等Unix-like系统设计的&#xff0c;虽然在Windows上也有版本&#xff0c;但可能不是…

【Redis】Redis 持久化机制详解:RDB、AOF 和混合持久化的工作原理及优劣分析

目录 持久化RDB触发机制流程说明RDB ⽂件的处理RDB 的优缺点 AOF使⽤ AOF命令写⼊⽂件同步重写机制启动时数据恢复 混合持久化小结 持久化 回顾 MySQL 的事务的特性&#xff1a; 原子性一致性持久性&#xff08;持久化&#xff09;隔离性 持久化&#xff1a;把数据存储在硬盘上…

RabbitMQ 02 操作,配置信息,用户权限

01.介绍启动&#xff0c;关闭 02.环境 2.1 MQ是用Erlang语言写的 2.2 一个RabbitMQ 节点 一个 Erlang节点一个Erlang 程序 &#xff08;RabbitMQ程序&#xff09; 2.3 Erlang节点&#xff1a; 这个是Erlang节点集群状态下&#xff1a; 2.4 启动节点 2.5 查看日志信息 …