【Rust中多线程同步机制】

Rust中多线程同步机制

  • 多线程编程
  • Rust中的多线程编程
    • thread::spawn
    • move
  • Rust中的线程间同步机制
    • Rust线程间同步机制之Mutex
    • Rust线程间同步机制之读写锁
    • Rust线程同步机制之条件变量
    • Rust中的信号量
    • Rust中的Atomic
  • Rust中线程间的数据传递
  • 总结


多线程编程

多线程编程,在rust异步编程中我们提到过:

线程用于并行工作,异步用于并行等待

并行即一对一处理任务,并发即M对N轮换处理任务,看起来像是同步在运行的程序实际上在循环更替交替占用cpu核。


Rust中的多线程编程

thread::spawn

Spawns a new thread, returning a JoinHandle for it.
spawn方法用于创建一个新线程并将返回一个Join句柄
join句柄很好理解,即在多线程编程中,常见的我们一般会在主线程做子线程的等待回收,此返回值便是如此。

代码示例:

use std::thread;
fn main() {let handler = thread::spawn(|| {// thread codeprintln!("this is a thread");});handler.join().unwrap();//显然join会返回一个Result结果
}

move

代码示例:

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

move表示对vec所有权的转移。因为thread::spawn表示创建一个新的线程,我们无法获知新线程的生命周期,所以当子线程使用到主线程的中的变量,通常情况下需要将所有权也进行转移。

Rust中的线程间同步机制

与其他语言一样,Rust也提供了诸如互斥锁,读写锁,条件变量,信号量等的线程间数据同步机制,下面一一进行举例说明:

为保证程序性能,一尽量避免线程间需要的同步,二如果一定需要同步,选择合适的同步机制以及细粒度。

Rust线程间同步机制之Mutex

代码示例:

use std::sync::{Arc, Mutex};
use std::thread;fn main() {let counter = Arc::new(Mutex::new(10));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());
}

Arc是线程安全的是不是意味着可以直接让其携带数据?–不是的,Arc的线程安全保证的是这个智能指针的计数在多线程中是完全保证其原子性的,但是我发保证其所指的数据是安全的,所以在Rust的多线程编程中,Arc常与Mutex共同使用,除非所指数据并不需要修改。

上述代码中我们使用了Vec来存储joinhander,并在后续的for循环中依次收集等待了子线程的运行结束,如果不这样做而让其自行结束会发生什么?
由于创建线程更加的耗时,所以会出现打印结果大于0的情况,因为一部分子线程并没有等到运行结束而是跟随主线程一同消亡了。

简而言之,如果开发者需要一个在多线程间同步的数据,并且需要在线程间修改,那么使用Arc+Mutex是没有问题的。

Rust线程间同步机制之读写锁

Rust中的读写锁与其他语言中的并无二致,读写锁允许同时读,但同一时刻有且只能有一个写。在编程时为了避免死锁以及非规范写法带来的程序风险,使用TryLockxx是很好的。
代码示例:

use std::sync::RwLock;fn main() {let lock = RwLock::new(5);// many reader locks can be held at once{let r1 = lock.read().unwrap();let r2 = lock.read().unwrap();assert_eq!(*r1, 5);assert_eq!(*r2, 5);} // read locks are dropped at this point// only one write lock may be held, however{let mut w = lock.write().unwrap();*w += 1;assert_eq!(*w, 6);} // write lock is dropped here
}

Rust线程同步机制之条件变量

条件变量一般和锁配合使用,既等待条件变量的线程会在等待出获得锁在进入条件变量等待后释放锁等待唤醒,而唤醒线程会使用notice,broadcast等方法通知到等待线程,进而线程恢复执行态,带着锁向下执行。这是很经典的生产者消费者模型。

代码示例:

use std::sync::{Arc, Condvar, Mutex};
use std::thread;fn main() {let pair = Arc::new((Mutex::new(false), Condvar::new()));  //创建锁和条件变量let pair2 = Arc::clone(&pair);// Inside of our lock, spawn a new thread, and then wait for it to start.thread::spawn(move || {let (lock, cvar) = &*pair2;let mut started = lock.lock().unwrap();*started = true;// We notify the condvar that the value has changed.cvar.notify_one();//通知线程});// Wait for the thread to start up.let (lock, cvar) = &*pair;let mut started = lock.lock().unwrap();while !*started {started = cvar.wait(started).unwrap();//等待线程}
}

Rust中的信号量

信号量一般指系统层面的控制资源的如C接口sem_init,sem_wait,sem_post等,信号量本身就是原子性的,通过PV操作,既增加或减持来保证数据的统一性和资源的同步性,一般用在共享内存的同步机制,如共享内存的数据队列等,在Rust中,原理是一样的,都是通过PV操作来保证数据的一致性。Rust中的信号量关键字是Semaphore。
代码示例:

use tokio::sync::{Semaphore, TryAcquireError};
#[tokio::main]
async fn main() {let semaphore = Semaphore::new(3);let a_permit = semaphore.acquire().await.unwrap();let two_permits = semaphore.acquire_many(2).await.unwrap();assert_eq!(semaphore.available_permits(), 0);let permit_attempt = semaphore.try_acquire();assert_eq!(permit_attempt.err(), Some(TryAcquireError::NoPermits));
}

Rust中的Atomic

标准库中的Atomic:

AtomicBool	A boolean type which can be safely shared between threads.
AtomicI8	An integer type which can be safely shared between threads.
AtomicI16	An integer type which can be safely shared between threads.
AtomicI32	An integer type which can be safely shared between threads.
AtomicI64	An integer type which can be safely shared between threads.
AtomicIsize	An integer type which can be safely shared between threads.
AtomicPtr	A raw pointer type which can be safely shared between threads.
AtomicU8	An integer type which can be safely shared between threads.
AtomicU16	An integer type which can be safely shared between threads.
AtomicU32	An integer type which can be safely shared between threads.
AtomicU64	An integer type which can be safely shared between threads.
AtomicUsize	An integer type which can be safely shared between threads.

Atomic仅支持内置类型的原子性操作,一般情况开发者使用不到,只有在编写库的时候可能会用到,其使用CAS机制保证了数据的线程安全性。

代码示例:

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{hint, thread};fn main() {let spinlock = Arc::new(AtomicUsize::new(1));let spinlock_clone = Arc::clone(&spinlock);let thread = thread::spawn(move || {spinlock_clone.store(0, Ordering::Release);});// Wait for the other thread to release the lockwhile spinlock.load(Ordering::Acquire) != 0 {hint::spin_loop();}if let Err(panic) = thread.join() {println!("Thread had an error: {panic:?}");}
}

Rust中线程间的数据传递

一般情况下,我们在使用C++多线程时,会使用同步机制保证数据的一致性,以及共享数据的安全性。在Rust中不仅仅可以使用线程间同步的方式,如加锁,信号量,条件变量等,一般的在C++中提及数据传递一般发生在进程间,如管道,队列等等,rust对线程间的数据传递 也进行了友好的支持如Module std::sync::mpsc。

Module std::sync::mpsc 即Multi-producer, single-consumer FIFO queue communication primitives.
发送与接收在第一次使用时便可确定数据类型,并在使用期间不可变化,如果想发送不同的数据类型,则需要使用enum包裹。

代码示例:

use std::sync::mpsc::channel;
use std::thread;fn main() {// Create a shared channel that can be sent along from many threads// where tx is the sending half (tx for transmission), and rx is the receiving// half (rx for receiving).let (tx, rx) = channel();for i in 0..10 {let tx = tx.clone();thread::spawn(move || {tx.send(i).unwrap();//无接收者时,send将会返回错误});}for _ in 0..10 {let j = rx.recv().unwrap();//发送者全部消失时,recv将会返回错误(阻塞模式,由发送者recv就会一直等待),try——recv仅做接收尝试,可以接收不到数据(既不阻塞)assert!(0 <= j && j < 10);}
}

只有实现了Sync+Send的数据类型方为线程安全的,对于自组织类型,如果其中一个没有实现,那么整个都无法是线程安全的。并且一般情况下开发者无需 手动实现Sync+Send,当程序报错时首先考虑是不是自己的定义有问题


总结

Rust语言的线程间同步机制大概就这么多,需要不断的熟悉才能掌握。

“不确定的未来才是最确定的”

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

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

相关文章

传统RAG流程;密集检索器,稀疏检索器:中文的M3E

目录 传统RAG流程 相似性搜索中:神经网络的密集检索器,稀疏检索器 密集检索器 BGE系列模型 text-embedding-ada-002模型 M3E模型 稀疏检索器 示例一:基于TF-IDF的稀疏检索器 示例二:基于BM25的稀疏检索器 稀疏检索器的特点与优势 传统RAG流程 相似性搜索中:神经…

黑马程序员linux学习【持续更新】

Linux基础 一、Linux简介 1.分类 不同领域的主流操作系统&#xff0c;主要分为下 几类&#xff1a;桌面操作系统、服务器操作系统、移动设备操作系统、嵌入式操作系统。 桌面操作系统 操作系统特点Windows用户数量最多MacOS操作体验好&#xff0c;办公人士首选Linux用户数…

2024强网杯Proxy

代码审计 首先分析go语言代码 package mainimport ("bytes""io""net/http""os/exec""github.com/gin-gonic/gin" )type ProxyRequest struct {URL string json:"url" binding:"req…

02多线程基础知识

目录 1. 线程与进程 进程&#xff08;Process&#xff09; 线程&#xff08;Thread&#xff09; 2. 并发和并行 并发&#xff08;Concurrency&#xff09; 并行&#xff08;Parallelism&#xff09; 3. CPU 调度 定义 类型 调度算法 上下文切换 4.线程间的状态流转…

Spring常用过滤器(Filter)-AuthorizationFilter

AuthorizationFilter&#xff1a;授权过滤器&#xff0c;用于执行访问控制决策。 1.1 定义与作用&#xff1a; 1.1.1 定义&#xff1a;AuthorizationFilter是ASP.NET MVC中用于安全性检查的过滤器&#xff0c;它通过实现IAuthorizationFilter接口来定义。该接口提供了一个OnAu…

java中函数式接口

函数式接口 supplier提供者 无中生有 () -> 结果 function 函数 一个参数一个结果 (参数) -> 结果 两个参数一个结果的 BiFunction (参数1,参数2) -> 结果 consumer 消费者 一个参数没有结果 (参数) -> void 两个参数的 BiConsumer (参数1,参数2) -…

np.clip函数

np.clip 是 NumPy 中的一个函数&#xff0c;用于将数组中的元素限制在指定的范围内&#xff08;即对数组的值进行截断&#xff09;。超出范围的值会被替换为指定的上下限值。 函数语法 numpy.clip(a, a_min, a_max, outNone, *, whereTrue)参数说明 a&#xff1a; 输入的数组&…

brainpy 动力学编程基础

文章参考&#xff1a; 《神经计算建模实战——基于brainpy》 吴思 【brainpy学习笔记】基础知识2(动力学模型的编程基础)-CSDN博客 Brainpy手册 文章目录 积分器&#xff1a;定义ODE函数数值积分方法 更新函数和动力系统计算介绍什么是brainpy.DynamicalSystem&#xff1f;如…

macos中安装和设置ninja

1、在安装ninja的过程中需要先安装re2c(github地址&#xff1a;https://github.com/skvadrik/re2c): git clone https://github.com/skvadrik/re2c.git&#xff08;也可直接下载最新的release压缩包&#xff0c;并解压。下载地址&#xff1a;https://github.com/skvadrik/re2c…

Go 项目中实现类似 Java Shiro 的权限控制中间件?

序言&#xff1a; 要在 Go 项目中实现类似 Java Shiro 的权限控制中间件&#xff0c;我们可以分为几个步骤来实现用户的菜单访问权限和操作权限控制。以下是一个基本的实现框架步骤&#xff1a; 目录 一、数据库设计 二、中间件实现 三、使用中间件 四、用户权限管理 五…

数据结构之二叉树--前序,中序,后序详解(含源码)

二叉树 二叉树不能轻易用断言&#xff0c;因为树一定有空 二叉树链式结构的实现 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。 typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType _data;struct B…

市场营销应该怎么学?

别一听市场营销就觉得是那些大公司玩的高深莫测的游戏&#xff0c;其实它就在你我身边&#xff0c;无处不在&#xff0c;影响着咱们生活的方方面面。 记得去年双十一&#xff0c;你是不是被各种优惠券、预售、秒杀整得头晕眼花&#xff0c;最后还是忍不住剁了手&#xff1f; …

【NativeUI下的data table备注信息的快捷输入-会议签到补充】

NativeUI下的data table备注信息的快捷输入-会议签到补充 概述结构本文任务子组件在列中定制显示父组件的备注补充父组件的便捷输入按钮父组件快捷按钮给子组件的备注用最后固定在底部 概述 本文讲述Vue3的数据和函数在父组件,子组件的交互,以NativeUI的datatable为载体,实现签…

一七五、HTML 不同类型的事件及其说明和示例

HTML 事件处理程序是通过 JavaScript 来捕获和响应不同的用户操作、系统事件或浏览器事件。下面是不同类型的事件及其说明和示例。 Window 事件 1. onresize 当浏览器窗口的大小发生变化时触发。 <!DOCTYPE html> <html lang"en"> <head><m…

从本地到云端:Linux上快速搭建Cloudreve云盘并实现远程管理

文章目录 前言1. 安装Docker2. 使用Docker拉取镜像3. 创建并启动Cloudreve容器4. 本地访问测试5. 公网远程访问本地Cloudreve5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 前言 大家好&#xff01;今天我们要聊聊如何在Linux系统上&#xff0c;…

如何简化App Store提现?——作为游戏开发者的跨境收款体验分享

目录 如何简化App Store提现&#xff1f;——作为游戏开发者的跨境收款体验分享跨境收款常见的几个问题使用万里汇收款后的体验1. 结算流程简单&#xff0c;到账更快2. 多场景收付更灵活3. 多种支付方式支持 使用后的效果&#xff1a;资金管理更高效个人建议 如何简化App Store…

JavaScript void 运算符

void定义&#xff1a; void 运算符对给定的表达式进行求值&#xff0c;然后返回undefined。void是一个一元运算符&#xff0c;接受单个操作数&#xff0c;可以是任何类型&#xff0c;返回一个 undefined。 void语法&#xff1a; void 在表达式的左边&#xff0c;void 右边的…

Apache DolphinScheduler + OceanBase,搭建分布式大数据调度平台的实践

本文整理自白鲸开源联合创始人&#xff0c;Apache DolphinScheduler PMC Chair&#xff0c;Apache Foundation Member 代立冬的演讲。主要介绍了DolphinScheduler及其架构、DolphinScheduler与OceanBase 的联合大数据方案。 DolphinScheduler是什么&#xff1f; Apache Dolph…

Java字符串深度解析:String的实现、常量池与性能优化

引言 在Java编程中&#xff0c;字符串操作是最常见的任务之一。String 类在 Java 中有着独特的实现和特性&#xff0c;理解其背后的原理对于编写高效、安全的代码至关重要。本文将深入探讨 String 的实现机制、字符串常量池、不可变性的优点&#xff0c;以及 String、StringBu…

快速上手vue3+js+Node.js

安装Navicat Premium Navicat Premium 创建一个空的文件夹&#xff08;用于配置node&#xff09; 生成pakeage.json文件 npm init -y 操作mysql npm i mysql2.18.1 安装express搭建web服务器 npm i express4.17.1安装cors解决跨域问题 npm i cors2.8.5创建app.js con…