rust 多线程

Rust语言的多线程编程 多线程编程是提高程序性能、充分利用多核处理器的有效手段。在现代计算机系统中,多核处理器已成为标准,这使得并行计算的重要性愈加突出。Rust语言凭借其独特的内存安全特性和并发编程模型,成为了多线程编程的热门选择。在本文中,我们将探讨Rust语言的多线程编程,包括其核心概念、使用场景,以及如何解决多线程中的常见问题。 阅读详情

多线程并发编程

使用多线程

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

有几点值得注意:

  • 线程内部的代码使用闭包来执行
  • main 线程一旦结束,程序就立刻结束,因此需要保持它的存活,直到其它子线程完成自己的任务
  • thread::sleep 会让当前线程休眠指定的时间,随后其它线程会被调度运行,因此就算你的电脑只有一个 CPU 核心,该程序也会表现的如同多 CPU 核心一般,这就是并发!
在线程闭包中使用 move

move 关键字在闭包中的使用可以让该闭包拿走环境中某个值的所有权,同样地,你可以使用 move 来将所有权从一个线程转移到另外一个线程。

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

    // 下面代码会报错borrow of moved value: `v`
    // println!("{:?}",v);
}
线程局部变量(Thread Local Variable)
标准库
use std::cell::RefCell;
use std::thread;

thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

FOO.with(|f| {
    assert_eq!(*f.borrow(), 1);
    *f.borrow_mut() = 2;
});

// 每个线程开始时都会拿到线程局部变量的FOO的初始值
let t = thread::spawn(move|| {
    FOO.with(|f| {
        assert_eq!(*f.borrow(), 1);
        *f.borrow_mut() = 3;
    });
});

// 等待线程完成
t.join().unwrap();

// 尽管子线程中修改为了3,我们在这里依然拥有main线程中的局部值:2
FOO.with(|f| {
    assert_eq!(*f.borrow(), 2);
});

第三方库
use thread_local::ThreadLocal;
use std::sync::Arc;
use std::cell::Cell;
use std::thread;

let tls = Arc::new(ThreadLocal::new());

// 创建多个线程
for _ in 0..5 {
    let tls2 = tls.clone();
    thread::spawn(move || {
        // 将计数器加1
        let cell = tls2.get_or(|| Cell::new(0));
        cell.set(cell.get() + 1);
    }).join().unwrap();
}

// 一旦所有子线程结束,收集它们的线程局部变量中的计数器值,然后进行求和
let tls = Arc::try_unwrap(tls).unwrap();
let total = tls.into_iter().fold(0, |x, y| x + y.get());

// 和为5
assert_eq!(total, 5);

线程间的消息传递

use std::sync::mpsc;
use std::thread;

fn main() {
    // 创建一个消息通道, 返回一个元组:(发送者,接收者)
    let (tx, rx) = mpsc::channel();

    // 创建线程,并发送消息
    thread::spawn(move || {
        // 发送一个数字1, send方法返回Result<T,E>,通过unwrap进行快速错误处理
        tx.send(1).unwrap();

        // 下面代码将报错,因为编译器自动推导出通道传递的值是i32类型,那么Option<i32>类型将产生不匹配错误
        // tx.send(Some(1)).unwrap()
    });

    // 在主线程中接收子线程发送的消息并输出
    println!("receive {}", rx.recv().unwrap());
}
  • 若值的类型实现了Copy特征,则直接复制一份该值,然后传输过去,例如之前的i32类型
  • 若值没有实现Copy,则它的所有权会被转移给接收端,在发送端继续使用该值将报错
使用多发送者
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx1 = tx.clone();
    thread::spawn(move || {
        tx.send(String::from("hi from raw tx")).unwrap();
    });

    thread::spawn(move || {
        tx1.send(String::from("hi from cloned tx")).unwrap();
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

以上代码并不复杂,但仍有几点需要注意:

  • tx,rx对应发送者和接收者,它们的类型由编译器自动推导: tx.send(1)发送了整数,因此它们分别是mpsc::Sender和mpsc::Receiver类型,需要注意,由于内部是泛型实现,一旦类型被推导确定,该通道就只能传递对应类型的值, 例如此例中非i32类型的值将导致编译错误
  • 接收消息的操作rx.recv()会阻塞当前线程,直到读取到值,或者通道被关闭
  • 需要使用move将tx的所有权转移到子线程的闭包中
同步和异步通道
异步通道
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
    let (tx, rx)= mpsc::channel();

    let handle = thread::spawn(move || {
        println!("发送之前");
        tx.send(1).unwrap();
        println!("发送之后");
    });

    println!("睡眠之前");
    thread::sleep(Duration::from_secs(3));
    println!("睡眠之后");

    println!("receive {}", rx.recv().unwrap());
    handle.join().unwrap();
}
同步通道
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
    // 设置消息队列大小
    let (tx, rx)= mpsc::sync_channel(0);

    let handle = thread::spawn(move || {
        println!("发送之前");
        tx.send(1).unwrap();
        println!("发送之后");
    });

    println!("睡眠之前");
    thread::sleep(Duration::from_secs(3));
    println!("睡眠之后");

    println!("receive {}", rx.recv().unwrap());
    handle.join().unwrap();
}
传输多种类型的数据
use std::sync::mpsc::{self, Receiver, Sender};

enum Fruit {
    Apple(u8),
    Orange(String)
}

fn main() {
    let (tx, rx): (Sender<Fruit>, Receiver<Fruit>) = mpsc::channel();

    tx.send(Fruit::Orange("sweet".to_string())).unwrap();
    tx.send(Fruit::Apple(2)).unwrap();

    for _ in 0..2 {
        match rx.recv().unwrap() {
            Fruit::Apple(count) => println!("received {} apples", count),
            Fruit::Orange(flavor) => println!("received {} oranges", flavor),
        }
    }
}

线程同步:锁、Condvar 和信号量

共享内存可以说是同步的灵魂,因为消息传递的底层实际上也是通过共享内存来实现,两者的区别如下:

  • 共享内存相对消息传递能节省多次内存拷贝的成本

  • 共享内存的实现简洁的多

  • 共享内存的锁竞争更多
    消息传递适用的场景很多,我们下面列出了几个主要的使用场景:

  • 需要可靠和简单的(简单不等于简洁)实现时

  • 需要模拟现实世界,例如用消息去通知某个目标执行相应的操作时

  • 需要一个任务处理流水线(管道)时,等等

互斥锁 Mutex
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    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());
}
读写锁 RwLock
use std::sync::RwLock;

fn main() {
    let lock = RwLock::new(5);

    // 同一时间允许多个读
    {
        let r1 = lock.read().unwrap();
        let r2 = lock.read().unwrap();
        assert_eq!(*r1, 5);
        assert_eq!(*r2, 5);
    } // 读锁在此处被drop

    // 同一时间只允许一个写
    {
        let mut w = lock.write().unwrap();
        *w += 1;
        assert_eq!(*w, 6);

        // 以下代码会panic,因为读和写不允许同时存在
        // 写锁w直到该语句块结束才被释放,因此下面的读锁依然处于`w`的作用域中
        // let r1 = lock.read();
        // println!("{:?}",r1);
    }// 写锁在此处被drop
}

同时允许多个读,但最多只能有一个写
读和写不能同时存在
读可以使用read、try_read,写write、try_write, 在实际项目中,try_xxx会安全的多

线程同步:Atomic 原子类型与内存顺序

原子指的是一系列不可被 CPU 上下文交换的机器指令,这些指令组合在一起就形成了原子操作。在多核 CPU 下,当某个 CPU 核心开始运行原子操作时,会先暂停其它 CPU 内核对内存的操作,以保证原子操作不会被其它 CPU 内核所干扰。

use std::ops::Sub;
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread::{self, JoinHandle};
use std::time::Instant;

const N_TIMES: u64 = 10000000;
const N_THREADS: usize = 10;

static R: AtomicU64 = AtomicU64::new(0);

fn add_n_times(n: u64) -> JoinHandle<()> {
    thread::spawn(move || {
        for _ in 0..n {
            R.fetch_add(1, Ordering::Relaxed);
        }
    })
}

fn main() {
    let s = Instant::now();
    let mut threads = Vec::with_capacity(N_THREADS);

    for _ in 0..N_THREADS {
        threads.push(add_n_times(N_TIMES));
    }

    for thread in threads {
        thread.join().unwrap();
    }

    assert_eq!(N_TIMES * N_THREADS as u64, R.load(Ordering::Relaxed));

    println!("{:?}",Instant::now().sub(s));
}
Atomic 能替代锁吗

对于复杂的场景下,锁的使用简单粗暴,不容易有坑
std::sync::atomic包中仅提供了数值类型的原子操作:AtomicBool, AtomicIsize, AtomicUsize, AtomicI8, AtomicU16等,而锁可以应用于各种类型
在有些情况下,必须使用锁来配合,例如上一章节中使用Mutex配合Condvar

基于 Send 和 Sync 的线程安全

rust笔记10-多线程 Send 和 Sync 是 Rust 中用于确保线程安全的关键 trait。 多线程间的数据传送可以通过通道或共享内存实现。 同步机制包括 Mutex、RwLock、Barrier 和 Condvar 等。 阅读详情

相关推荐

thread_local-rs:Rust的按对象线程本地存储

thread_local 该库提供ThreadLocal类型,该类型允许将每个对象的单独对象副本使用。 与标准库的thread_local!不同,这允许按对象进行线程本地存储thread_local! 宏,仅允许静态本地线程存储。 用法 将此添加到您的Cargo.toml : [ dependencies ] thread_local = " 1.1 " 最低Rust版本 该板条箱的最低支持Rust版本(MSRV)为1.36.0。 执照 根据以下任一许可 Apache许可证2.0版( 或 ) MIT许可证( 或 ) 由您选择。 贡献 除非您明确声明,否则您有意提交以供您包含在工作中的任何贡献(按照Apache-2.0许可的定义)均应如上所述获得双重许可,且无任何其他条款或条件。

Rust开发——Rust开发中thread_local

在 Rust 中,thread_local 宏用于创建线程本地存储,允许每个线程都拥有其独立的副本,这与所有权概念有所不同。 所有权(Ownership)是 Rust 中的一个核心概念,它用于管理内存的分配和释放,确保在编译时避免出现数据竞争、悬垂指针和多线程安全问题。所有权规则强制确保每个值都只有一个所有者,并通过移动、借用和生命周期来管理值的传递和使用。

知来者逆的博客 1751

Pikachu靶场环境搭建(Windows环境下安装详细教程)

Pikachu靶场是一个漏洞测试练习平台。其中包含了常见的web安全漏洞,如果你是一个渗透测试学习者没有靶场的练手,那么pikachu是一个很好的测试平台。

weixin_42857656的博客 1823

rust实战系列一百六十四:线程局部存储

Mutex/RwLock在加锁的时候返回的是Result类型,是因为它。(2)RefCell是非线程安全的,它不能在跨线程场景使用。声明的变量,使用的时候要用with()方法加闭包来完成。与它相对应的线程安全版本。panic,导致Mutex内部的数据已经被破坏,而在另外一个线程中依然有可能观察到。线程局部(Thread Local)的意思是,声明的这个变量看起来是一个变量,但。这既提供了完整的功能,又避免了数据竞争一类的bug。正因为在“内存安全”问题上的一系列基础性设计,才导致了“线程安全”基本就。

Mr_Roki的博客 789

TS808效果器制作教程

TS808效果器制作教程 可以在立创商城购买到大部分的电阻电容和IC,音频接口,踩钉,电位器和电源接口可以去某宝看看都买得到的。链接https//pan.baidu.com/s/1QId1eIE3fJ2u5NLBSISvGA。安装过程过于繁琐,把所有元器件按照图纸焊接完成就可以调试了,祝你们成功!这是TS808经典效果器制作过程的记录。IC是JRC4558。...

qq_18948149的博客 3422

网络抓包实战:如何捕获与解析802.1Q VLAN标签报文

在网络通信中,数据包是信息传输的基本单元,而VLAN(虚拟局域网)技术通过在数据链路层插入802.1Q标签,实现了网络流量的逻辑隔离与高效管理。其核心原理是在标准以太网帧头中嵌入4字节的VLAN标识,使交换机能够根据VID(VLAN ID)进行精准转发。掌握VLAN报文的捕获与解析技术,对于网络故障排查、安全审计和性能优化具有重要价值,尤其在需要验证网络隔离策略或诊断Trunk链路异常时。本文聚焦于使用Wireshark工具,结合端口镜像等实操方法,详细演示如何抓取并解读携带802.1Q标签的原始报文,帮助

weixin_33417488的博客 381

rust里使用thread_local!

thread_local使用很简单,只需要使用这个宏就可以了,不需要任何的use,但是释放资源的时候一定要注意,实现Drop是不可行的下面的代码的Drop是不可行的:use std::ops::Drop;struct Foo;impl Foo{ fn print(&self){ println!("print foo"); } //fn drop(&self){

varding的专栏 6747

Tlsr8258开发-程序烧写

开源一小步,进步一大步

自学嵌入式 1773

Rust futures: async fn 中的 thread::sleep 和阻塞调用

原文:Rust futures: thread::sleep and blocking calls inside async fn URL: https://blog.hwc.io/posts/rust-futures-threadsleep-and-blocking-calls-inside-async-fn/ 近来,关于 Rust 的futures和async/await如何工作(“blockers”,哈哈),我看到存在一些普遍的误解。很多新用户为async/await带来的重大改进而感到兴.

跨链技术践行者 1418

西门子S7-200 SMART PLC控制伺服电机

以下是使用西门子S7-200 SMART PLC控制伺服电机的详细程序框架和配置说明,涵盖硬件接线、PLC配置和基本运动控制逻辑。具体实现需根据伺服电机型号(如松下、三菱、台达等)调整参数。通过以上步骤,可实现伺服电机的基础位置控制。实际应用需根据设备机械参数和伺服驱动器手册调整细节。建议在调试时逐步测试脉冲输出和方向信号是否正常。

CHUXUEZHE8210的博客 6423

华为OD机考双机位C卷 - 简单的解压缩算法 (Java & Python & JS & GO & C++ & C)

华为OD机考双机位C卷 - 简单的解压缩算法 , 提供Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。

算法大师! 128

详解Rust多线程编程

Rust的多线程编程提供了一种安全、高效的方式来进行并发操作。Rust的并发性设计原则之一是确保线程安全,同时避免运行时的开销,这使得它在多线程编程上有显著的优势。Rust通过其所有权系统、类型系统和并发原语,能够在编译时避免数据竞争和内存错误,从而使并发编程变得更安全。

yang1fei2的博客 1589

PCB制造工艺参数到AD规则设置

本文提供了PCB设计与制造的落地指南,强调需先了解PCB厂的工艺参数,再在Altium Designer中设置相应规则。核心步骤包括:获取PCB厂的关键工艺参数(线宽/线距、过孔尺寸等),在AD中设置电气规则、布线规则、阻焊规则等,并进行DRC检查。文章还给出了常见问题避坑建议和快速落地模板,帮助设计师确保设计符合生产要求,避免因工艺不匹配导致的报废或成本增加。

qq_37999850的博客 292

Rust多线程编程概述

这个实例的输出结果是随机的,主线程一旦执行完成,程序就会自动退出,不会继续等待子线程。线程是进程(Process)中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。事实上,如果线程及其返回值可以比它们的调用程序更持久,我们需要确保它们在之后是有效的,因为我们不知道它们什么时候会返回,所以需要让它们尽可能长时间地有效,也就是说,直到程序结束,因此是“静态生存期”。

夏天又到了的专栏 1746

【人工智能之深度学习】21. 自动驾驶感知实战:YOLOv4复杂天气优化方案(BDD100K全代码+效果对比)

摘要:自动驾驶摄像头在雨雾天气下易受图像模糊、目标遮挡影响,导致YOLOv4检测精度骤降。本文基于BDD100K公开数据集(含多天气场景),设计三阶段优化方案:1)物理建模生成雨雾效果(雨滴线条+大气散射模型),将恶劣天气样本占比从20%提升至40%;2)在YOLOv4颈部嵌入CBAM注意力机制,强化模糊区域特征提取;3)L1剪枝(30%)+TensorRT量化实现轻量化。需特别说明:本文为教学虚拟案例,不可直接用于车载部署,实际落地需结合多传感器融合与ISO 26262认证。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1681

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Rust 多线程编程

一个进程一定有一个主线程,主线程之外创建出来的线程称为子线程多线程编程,其实就是在主线程之外创建子线程,让子线程和主线程并发运行,完成各自的任务。Rust语言支持多线程编程。Rust语言标准库中的 std::thread 模块用于多线程编程。std::thread 提供很很多方法用于创建线程、管理线程和结束线程。

void的博客 910

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

rust多线程

tx,rx对应发送者和接收者,它们的类型由编译器自动推导: tx.send(1)发送了整数,因此它们分别是mpsc::Sender和mpsc::Receiver类型,需要注意,由于内部是泛型实现,一旦类型被推导确定,该通道就只能传递对应类型的值。在上面代码中,主线程和子线程是并发运行的,子线程在不停的发送消息 -> 休眠 1 秒,与此同时,主线程使用for循环阻塞的从rx迭代器中接收消息,当子线程运行完成时,发送者tx会随之被drop,此时for循环将被终止,最终main线程成功结束。

xglinux的博客 809

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

【Rust中多线程同步机制】

Rust语言的线程间同步机制大概就这么多,需要不断的熟悉才能掌握。读写锁,信号量,条件变量,atomic,Arc

m0_37719524的博客 1754
上一篇: 字节赫兹 框架教程 一请求上下文之请求
下一篇: 启航kp OpenHarmony环境搭建
过去日记
过去日记 新星创作者: 后端开发技术领域 新星创作者: 后端开发技术领域
博客等级 码龄3年 5342粉丝 225原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

过去日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值