Rust 学习笔记:共享状态并发

Rust学习笔记2025.6.16 本文分享了基于Rust的Web开发实践笔记,涵盖主流框架Axum、Actix-web和Rocket的应用示例。主要内容包括:1)使用Axum构建高性能供应商管理API;2)Actix-web实现视频管理系统和图书管理系统的完整案例;3)Rocket框架的快速开发体验;4)MySQL/PostgreSQL/MongoDB数据库集成方案;5)Tokio异步编程示例。文章通过具体代码展示了Rust在Web开发中的核心功能实现,包括路由配置、CRUD操作、表单处理、文件上传和地理空间查询等,为开发者提供了实用的技术 阅读详情

Rust 学习笔记:共享状态并发

消息传递是处理并发性的好方法,但不是唯一的方法。另一种方法是让多个线程访问相同的共享数据。

智能指针使多重所有权成为可能,多重所有权会增加复杂性,因为这些不同的所有者需要管理。Rust 的类型系统和所有权规则极大地帮助了这种管理的正确性。

让我们看看互斥锁,这是共享内存中比较常见的并发原语之一。

互斥锁

互斥只允许一个线程在任何给定的时间访问某些数据。要访问互斥锁中的数据,线程必须首先通过请求获取互斥锁来发出访问信号。lock 是一种数据结构,是互斥锁的一部分,它跟踪当前谁对数据具有独占访问权。

使用互斥锁的两条规则:

  1. 在使用数据之前,必须尝试获取锁。
  2. 在处理完互斥锁保护的数据后,必须解锁数据,以便其他线程可以获取该锁。

Mutex<T> 的 API

使用 new 函数创建 Mutex<T>。

为了访问互斥体内的数据,我们使用 lock 方法来获取锁。这个调用将阻塞当前线程,因此它不能做任何工作,直到轮到我们获得锁。

如果持有锁的另一个线程 panic,lock 方法将失败。在这种情况下,没有人能够获得锁,所以我们选择了 unwrap,并在这种情况下让线程 panic。

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m = {m:?}");
}

在获得锁之后,可以将返回值(在本例中名为 num)作为对内部数据的可变引用。类型系统确保我们在使用 m 中的值之前获得锁。m 的类型是Mutex<i32>,而不是 i32,所以我们必须调用 lock 才能使用 i32 的值。

lock 方法返回一个名为 MutexGuard 的智能指针,封装在调用 unwrap 时处理的 LockResult 中。MutexGuard 智能指针实现了 Deref 和 Drop trait。

当 MutexGuard 超出作用域时自动释放锁,这种情况发生在内部作用域的末尾。因此,我们不会冒忘记释放锁而阻塞互斥锁被其他线程使用的风险,因为锁的释放是自动发生的。

在解除锁之后,我们可以打印互斥锁的值,并看到我们能够将内部的 i32 更改为 6。

在这里插入图片描述

在多个线程之间共享互斥锁

现在让我们尝试使用 Mutex<T> 在多个线程之间共享一个值。

启动 10 个线程,并让每个线程将计数器的值增加 1。

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    let mut handles = vec![];

    for _ in 0..10 {
        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());
}

编译出错:

在这里插入图片描述

错误消息指出计数器值在循环的前一次迭代中被移动。Rust 告诉我们不能将互斥锁的所有权转移到多个线程中。让我们用多重所有权方法来修复错误。

多线程的多重所有权

我们知道使用智能指针 Rc<T> 可以将一个值赋给多个所有者。

修改之前的代码,用 Rc<T> 包装 Mutex<T>,并在将所有权转移到线程之前克隆 Rc<T>。

use std::rc::Rc;
use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Rc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Rc::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());
}

还是编译错误:

在这里插入图片描述

Rc<T> 不是线程安全的。当 Rc<T> 管理引用计数时,它将每次克隆调用的计数相加,并在每个克隆被删除时从计数中减去。但是它没有使用任何并发原语来确保计数的更改不会被另一个线程中断。这可能会导致错误的计数,进而导致内存泄漏或在我们完成之前丢弃值。

我们需要的是一种完全类似于 Rc<T> 的类型,但它以线程安全的方式对引用计数进行更改。

Arc<T>:原子引用计数类型

原子引用计数类型 Arc<T> 是一种类似 Rc<T> 的类型,在并发情况下使用是安全的。

原子是一种额外的并发原语,确保线程安全,但线程安全带来了性能损失。

Arc<T> 和 Rc<T> 具有相同的 API,因此我们通过更改 use 行、对 new 的调用和对 clone 的调用来修复程序。

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

程序运行成功,并输出了正确的结果:

在这里插入图片描述

注意,如果要进行简单的数值操作,标准库的 std::sync::atomic 模块提供了比 Mutex<T> 更简单的类型。这些类型提供对基本类型的安全、并发的原子访问。

在本例中,我们选择将 Mutex<T> 与基本类型一起使用,这样我们就可以专注于 Mutex<T> 是如何工作的。

RefCell<T>/Rc<T> 与 Mutex<T>/Arc<T> 的相似性

你可能已经注意到 Mutex<T> 提供了内部可变性。

就像我们使用 RefCell<T> 来改变 Rc<T> 中的内容一样,我们使用 Mutex<T> 来改变 Arc<T> 中的内容。

使用 Rc<T> 有创建引用循环的风险,其中两个 Rc<T> 值相互引用,导致内存泄漏。类似地,Mutex<T> 也有创建死锁的风险。当一个操作需要锁定两个资源,并且两个线程各自获得了其中一个锁,导致它们永远等待对方时,就会发生这种情况。

医药行业新零售项目数据分析报告——基于Excel的分析与可视化 不同平台客户支付金额用数字+百分比展示更直观。各大区支付金额分析较为粗浅,后续可加入不同大区人数、配送费等进行细分分析。图表略微单一,可采用组合图,运用两个及以上维度对数据进行分析。数据来源本项目数据来源于某机构的项目案例,以上分析内容均为本人原创,不涉嫌抄袭。原做项目,新平台首发。 阅读详情

相关推荐

FPGA矩阵键盘计算器Verilog实现与Quartus仿真全流程解析

本文详细解析了基于FPGA和Verilog的矩阵键盘计算器实现全流程,包括矩阵键盘扫描、状态机设计、算术运算逻辑及数码管显示驱动。通过Quartus仿真与Testbench测试,确保设计稳定性与准确性,适合数字逻辑设计初学者学习FPGA开发与硬件描述语言应用。

chair的专栏 742

Rust 学习笔记:关于共享状态并发的练习题

Rust 学习笔记:关于共享状态并发的练习题

ProgramNovice的博客 382

DeepSeek-OCR 本地部署使用教程(附完整环境配置与推理示例)

DeepSeek-OCR是由DeepSeek AI推出的开源OCR模型,支持高精度文档识别与结构化输出(如Markdown转换)。本文提供了详细的本地部署指南,包括系统要求(推荐Linux+A100 GPU)、环境配置(Python 3.12.9+PyTorch 2.6.0+CUDA 11.8)、模型下载及两种推理方式:高性能的vLLM(适合批量处理)和灵活的Transformers(适合调试)。支持多种分辨率模式和提示词模板,适用于合同识别、PDF转换等场景。常见问题包括显存优化和性能调优建议。

qq_58607032的博客 6720

Rust学习笔记(下)

笔记的内容主要参考与《Rust 程序设计语言》,一些也参考了《通过例子学 Rust》和《Rust语言圣经》。Rust学习笔记分为上中下,其它两个地址在Rust学习笔记(上)和Rust学习笔记(中)。

Fishermen_sail的博客 1416

AD2428与SigmaStudio:从节点自动配置与EEPROM存储实战

本文详细介绍了AD2428 A2B收发器与SigmaStudio开发环境的集成应用,重点讲解从节点自动配置与EEPROM存储的实战流程。通过硬件连接、属性配置、数据导出及嵌入式系统集成等步骤,实现设备上电自启动,提升汽车音频系统的可靠性与部署效率。

g8f9d0s1a2的博客 650

别再只盯着时序了!聊聊SDC里那些容易被忽略的‘配角’命令:工作条件、线载模型与面积约束

本文深入探讨了SDC(设计约束)中常被忽视的关键命令,包括set_operating_conditions、set_wire_load_model和set_max_area。这些‘配角’命令在芯片设计中扮演着至关重要的角色,影响时序收敛、功耗和面积优化。文章通过实际案例和最佳实践,揭示了如何正确配置工作条件、选择线载模型以及动态调整面积约束,以提升设计质量和效率。

weixin_26875109的博客 208

Rust 学习笔记:实现状态模式

Rust 学习笔记:实现状态模式

ProgramNovice的博客 1361

最佳RANSAC算法拟合矩形的实现(基于PCL库)

最佳RANSAC算法是一种常用的参数估计方法,用于从数据集中拟合出最佳的模型。在计算机视觉和图像处理领域,最佳RANSAC算法常用于拟合几何形状,如矩形。本文将详细介绍如何使用点云库(PCL)中的最佳RANSAC算法来拟合矩形,并提供相应的源代码。

JbPatch的博客 1541

Rust学习笔记_04——引用

在Rust中,当数据的生命周期结束时,它的内存会被自动释放。例如,如果一个函数返回了一个指向局部变量的引用,而该局部变量在函数返回后已经被销毁,那么返回的引用就是一个悬挂引用。悬挂引用(Dangling Reference)在Rust中是一个严重的内存错误,它指的是一个引用指向了一个已经被释放的内存位置。在Rust中,引用是处理借用操作的关键机制,它通过一系列的规则来保证内存安全和避免数据竞争。悬挂引用是内存安全问题的一种表现,它可能导致程序在运行时访问无效的内存地址,从而引发各种不可预测的行为。

LuckyLay的博客 1830

机器人外接 USB 音响完整配置指南

摘要:本文介绍了为机器人配置外接音响的方法。由于内置音响音量小且有散热噪音,建议使用Type-C接口外接音响。配置步骤包括:1)通过aplay -l命令查看音响端口信息;2)使用aplay命令测试音频播放;3)通过amixer命令设置音量。关键点在于正确识别音响的卡号(如card 2)和设备号(如device 0),并在命令中准确配置这些参数即可实现外接音响的正常使用。

m0_47999117的博客 356

MC_SetOverride指令详细应用介绍(基恩士PLC)

基恩士KV系列PLC和松下总线绝对值伺服运动控制(详细配置+轴控完整ST源代码)

RXXW_Dor的博客 334

深入并发编程:从 C++ 到 Rust 的学习笔记

多线程同时读写同一个变量会导致“数据竞争(Data Race)”,引发程序崩溃或结果错乱。今天重点攻克了两句让人懵逼的 Rust 并发代码,它们完美体现了 Rust 的严谨。:批处理是自动处理海量数据的任务模式。这是 Rust 中最爽的批处理优化。把庞大的数组切块,丢给不同线程处理。条件不满足就挂起休眠(不占 CPU),条件满足了由别人唤醒。:让每个子线程计算局部结果,最后由主线程汇总。:线程需要等待某个条件(如:有资源了)。:在批处理计算(如大范围求和)时,死循环会把 CPU 烧爆。

2401_88363404的博客 433

R语言实战应用精讲50篇(二)-多重线性回归系列之稳健回归

前言 线性回归的参数估计主要采用的是最小二乘法(又称最小平方法),该法是将使观测值与模型预测值之差的平方达到最小的值作为参数估计值。如果数据存在异常点或者异方差,最小二乘法估计会存在偏差,常用的处理策略就是改用稳健回归(Robust Regression)。 稳健回归就是采用更为稳健的参数估计方法来拟合模型,所谓稳健就是数据的波动对参数估计影响不大,比如不受异常值影响,或删除某一观测后对结果影响不大。稳健估计的方法有很多,主要有这么几类:最小绝对值回归(也称最小一乘法回归,lAV),最小中位数二乘法(L

wenyusuran的专栏 3160

Rust学习笔记_15——Union

是一种特殊的数据结构,允许你在同一个内存位置存储不同类型的值。中,所有的字段共享同一块内存,这意味着在任何给定时间,)有些相似,但有一个关键的不同点:在。只能安全地包含一个有效的值。在Rust编程语言中,

LuckyLay的博客 1303

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Rust多线程编程学习笔记

本文介绍了Rust语言中的多线程编程基础和实践方法。主要内容包括:同步线程编程(线程创建、通信、共享状态、返回值和线程池)、异步线程编程(Tokio运行时、异步任务和通道)、线程安全机制(所有权、同步原语、Send/Sync trait)以及性能优化技巧。Rust通过独特的所有权系统和类型系统保障线程安全,避免数据竞争问题。文章提供了详细的代码示例,展示如何实现线程间通信、状态共享和任务处理,并比较了同步与异步编程的特点和适用场景,为开发者提供了Rust多线程编程的全面指导。

lanzh_syf的博客 712

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

rust基础

rust基础学习笔记

fengxiandada的博客 1932

IDEA的使用指南

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

Rust8.2 Fearless Concurrency 无畏并发

Rust编程语言入门教程课程笔记 Lecture 16: Fearless Concurrency

Simba的博客 479

rust学习链接

序号 链接 00 rust学习02安装 01 rust学习01附录 02 rust学习04变量 03 rust学习03猜游戏 04 rust学习06所有权上 05 rust学习06所有权上 06 rust学习05函数 07 rust学习08结构示例与09方法 08 c++增强c类接口安全性与rust学习07切片 09 rust学习11匹配与c++部分头. 10 rust学习10枚举 11 rust学习13用与14前部分 12 rust学习12包...

个人进步之路 557

鸿蒙开发仓颉语言:并发机制的设计与实现

细粒度并发算法是相对于粗粒度算法而言的,粗粒度并发往往指使用并发度较低的方法实现对并发对象的访问,例如:使用一个锁对象控制所有线程对某一并发对象的操作,这种方式简单,但性能很差,任意时刻只允许一个线程操作并发并发,即:一个线程持有锁并操作并发对象时,其它线程会因为无法持有锁而被阻塞。例如,在示例代码 1 中,各线程最终将结果汇入并发哈希表 resultMap 中,注意到在插入结果时线程不需要通过额外的互斥锁保证正确性,因为并发哈希表内部已确保并发访问的正确性。而在它的视角中,仓颉线程被视为可执行任务。

adaedwa187545的博客 756

rust语言AI编程学习笔记(一)Tokio库(异步编程)

Tokio 是 Rust 的异步运行时,专为 IO 密集型任务设计。其核心采用反应器-执行器模型,通过轻量级任务实现高并发。主要特性包括:1) #[tokio::main]宏作为入口,支持多线程或单线程模式;2) tokio::spawn创建并发任务;3) join!并行等待多个任务;4) select!选择最先完成的任务;5) JoinSet管理动态任务组。共享状态通过异步锁(Mutex/RwLock)实现线程安全。Tokio 优势在于高效利用系统资源,特别适合网络服务等场景,但对纯CPU计算任务建议使用

咸甜适中 437

corutine rust_Rust学习笔记#5:函数和trait

函数基本语法Rust的函数使用fn关键字开头,函数可以有一系列的输入参数,还有一个返回类型。函数返回可以使用return语句,可以使用表达式。下面是一个标准函数的示例,add函数接受两个i32的参数,然后计算它们的和并返回:fn add(a: i32, b: i32) -> i32 {a + b}println!("{}", add(1, 2)); // 输出:3函数返回值如果不显示标明,...

weixin_39998462的博客 455

Rust 学习笔记 - 面向对象,java分布式开发面试题

Rust 避免将 struct 或 enum 称为对象,因为它们与 impl 块是分开的。trait 对象有些类似于其它语言中的对象,因为它们在某种程度上组合了数据与行为。trait 对象与传统对象又有不同的地方:无法为 trait 对象添加数据。trait 对象被专门用于抽象某些共有行为,它没有其它语言中的对象那么通用。下面来实现一下这个 GUI 工具:// lib.rs// 绘制一个按钮// main.rs// adder 是 crate 的名称// 绘制一个选择框。

2401_84411849的博客 1039

Rust 从“能跑”到“跑得对”:所有权、异步与工程化的双项目实战复盘(含压测与发布清单)

三个工程承诺:高性能(零成本抽象)、内存安全(编译期保证)、并发安全(无数据竞争)。度量视角延迟分布看 P50/P90/P99,而非平均值;资源曲线看吞吐/CPU/内存/FD 随负载变化的斜率;可观测性看结构化日志密度、Trace 覆盖率、指标可回归性。与传统栈差异:Rust 在“高吞吐 + 低延迟 + 可控内存 + 无 GC 抖动”的场景里让“正确性与性能兼得”,更适合边缘节点/高并发服务/系统工具/嵌入式。Rust 不是“银弹”,但它给了我们一种工程秩序。

2501_92698736的博客 990

Monoio:基于 io-uring 的高性能 Rust Runtime(1)

但是开发这类组件对开发者的专业水平有较高的要求,编程范式上对开发者心智负担巨大,稍有不慎就会造成非预期的后果。举例来说,在 C++ 中要完成一次异步的网络请求,需要将整个流程按照异步点拆分成独立的纯同步函数,并以 callback 的形式将其串联——这一来大大降低了其可读性,二来状态转换和管理容易出错;并且对变量生命周期需要精细管理,否则就会出现悬垂指针等内存问题。为什么不试试神奇的 Rust 呢?Rust 语言通过引入所有权模型,在不引入垃圾回收的情况下保证了内存安全;

2401_84874454的博客 438

Monoio:基于 io-uring 的高性能 Rust Runtime

过去,高性能网络中间件或服务器往往使用 C/C++ 编写,比如我们常见的 Envoy 和 Nginx。它们往往以非常直接的方式和操作系统交互,并且得益于没有垃圾收集机制,相比有 GC 的语言(如 Golang 和 Java),额外开销十分低,延迟稳定。但是开发这类组件对开发者的专业水平有较高的要求,编程范式上对开发者心智负担巨大,稍有不慎就会造成非预期的后果。举例来说,在 C++ 中要完成一次异步的网络请求,需要将整个流程按照异步点拆分成独立的纯同步函数,并以 callback 的形式将其串联——这一来大大

2301_82241819的博客 473
上一篇: Rust 学习笔记:关于消息传递的练习题
下一篇: Rust 学习笔记:关于共享状态并发的练习题
UestcXiye
博客等级 码龄8年 9028粉丝 1306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

UestcXiye

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

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

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

打赏作者

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

抵扣说明:

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

余额充值