【Rust中多线程同步机制】

news/2024/11/8 11:02:01/

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句柄很好理解,即在多线程编程中,常见的我们一般会在主线程做子线程的等待回收,此返回值便是如此。

代码示例:

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

move

代码示例:

rust">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

代码示例:

rust">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是很好的。
代码示例:

rust">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等方法通知到等待线程,进而线程恢复执行态,带着锁向下执行。这是很经典的生产者消费者模型。

代码示例:

rust">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。
代码示例:

rust">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:

rust">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机制保证了数据的线程安全性。

代码示例:

rust">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包裹。

代码示例:

rust">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.ppmy.cn/news/1545339.html

相关文章

【C/C++】strncpy函数的模拟实现

零.导言 之前我们学习了strncpy函数&#xff0c;不妨我们现在尝试模拟实现strncpy函数的功能。 一.实现strncpy函数的要点 strncpy函数是一种字符串函数&#xff0c;可以按字节拷贝字符类型的数组&#xff0c;因此我们自定义的模拟函数需要两个char类型的指针参数&#xff1b;…

html5拖放

1、什么是拖放&#xff08;Drag 和 Drop&#xff09; 拖放&#xff0c;字面意思就是拖动&#xff0c;放置 在编程里面也是如此,拖放是一种常见的特性&#xff0c;即抓取对象以后拖到另一个位置。 在 HTML5 中&#xff0c;拖放是标准的一部分&#xff0c;任何元素都能够拖放。…

【论文复现】自动化细胞核分割与特征分析

本文所涉及所有资源均在这里可获取。 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 论文复现 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 自动化细胞核分割与特征分析 引言效果展示HoverNet概述HoverNet原理分析整…

25国考照片处理器使用流程图解❗

1、打开“国家公务员局”网站&#xff0c;进入2025公务员专题&#xff0c;找到考生考务入口 2、点击下载地址 3、这几个下载链接都可以 4、下载压缩包 5、解压后先看“使用说明”&#xff0c;再找到“照片处理工具”双击。 6、双击后会进入这样的界面&#xff0c;点击&…

day20:三剑客——awk基础

一&#xff0c;概述 AWK 是一种用于处理文本和数据的编程语言&#xff0c;特别擅长用于处理格式化文本文件。它通过将输入数据分成字段&#xff0c;逐行进行处理&#xff0c;广泛应用于数据分析、文本处理和系统管理中。 二&#xff0c;使用方式 命令模式&#xff08;重点&a…

【循环引用及格式化输出】

垃圾回收机制 当一个值在内存中直接引用跟间接引用的量为0时&#xff0c;&#xff08;即这个值没有任何入口可以找到它&#xff09;那么这个值就会被清空回收♻️&#xff0c;释放内存空间&#xff1b; 列表在内存中的存储方式 1&#xff09;引用计数的两种方式 x "ea…

「Mac畅玩鸿蒙与硬件26」UI互动应用篇3 - 倒计时和提醒功能实现

本篇将带领你实现一个倒计时和提醒功能的应用&#xff0c;用户可以设置倒计时时间并开始计时。当倒计时结束时&#xff0c;应用会显示提醒。该项目涉及时间控制、状态管理和用户交互&#xff0c;是学习鸿蒙应用开发的绝佳实践项目。 关键词 UI互动应用倒计时器状态管理用户交互…

Objective-C 音频爬虫:实时接收数据的 didReceiveData_ 方法

在互联网技术领域&#xff0c;数据的获取和处理是至关重要的。尤其是对于音频内容的获取&#xff0c;实时性和效率是衡量一个爬虫性能的重要指标。本文将深入探讨在Objective-C中实现音频爬虫时&#xff0c;如何高效地使用didReceiveData:方法来实时接收数据&#xff0c;并通过…