【Rust中多线程同步机制】

news/2024/11/8 11:23:39 标签: rust, redis, 开发语言, 后端

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 code
        println!("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 lock
    while 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.niftyadmin.cn/n/5743822.html

相关文章

基于SSM(Spring + Spring MVC + MyBatis)框架的汽车租赁共享平台系统

基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的汽车租赁共享平台系统是一个复杂的Web应用程序&#xff0c;用于管理和调度汽车租赁服务。下面我将提供一个详细的案例程序概述&#xff0c;包括主要的功能模块和技术栈介绍。 项目概述 功能需求 用户管理&…

GitLab 中文发行版最新版重点功能解读

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

WiFi一直获取不到IP地址是怎么回事?

在当今这个信息化时代&#xff0c;WiFi已成为我们日常生活中不可或缺的一部分。无论是家庭、办公室还是公共场所&#xff0c;WiFi都为我们提供了便捷的无线互联网接入。然而&#xff0c;有时我们可能会遇到WiFi连接后无法获取IP地址的问题&#xff0c;这不仅影响了我们的网络使…

从0开始深度学习(26)——汇聚层/池化层

池化层通过减少特征图的尺寸来降低计算量和参数数量&#xff0c;同时增加模型的平移不变性和鲁棒性。汇聚层的主要优点之一是减轻卷积层对位置的过度敏感。 1 最大汇聚层、平均汇聚层 汇聚层和卷积核一样&#xff0c;是在输入图片上进行滑动计算&#xff0c;但是不同于卷积层的…

Word大珩助手:超大数字怎么读?35位数字?69位数字?

俄罗斯日前对谷歌开出了20000000000000000000000000000000000&#xff08;35位数字&#xff09;美元的罚款 这一数字远超全球GDP总和&#xff0c;消息一出很快就登上热搜。 面对这样一个庞大的数字&#xff0c;人们不禁好奇&#xff0c;这样的数字该如何读出来&#xff1f; …

【编程技巧】如何写好CMakeList【常见命令汇总】

【编程技巧】如何写好CMakeList【常见命令汇总】 一、问题背景 开发过程中经常需要构建新的C项目。受限于工期&#xff0c;往往草草从旧项目中抄一个CMakeList&#xff0c;要么是包含的头文件或者导入的库过多&#xff0c;加重了编译负担&#xff0c;要么就是删减过程中多删了…

从神经元到神经网络:深度学习的进化之旅

神经元、神经网络 神经元 Neuron )&#xff0c;又名感知机( Perceptron )&#xff0c;在模型结构上与 逻辑回归 一致&#xff0c;这里以一个二维输入量的例子对其进行进一步 的解释&#xff1a; 假设模型的输 入向 量是一 维特征向 (x1,x2). 则单神 经元的模型结构 如下…

IOS 防截屏实现

.h文件 #import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGINinterface HSNoScreenShotView : UIViewendNS_ASSUME_NONNULL_END .m文件 #import "HSNoScreenShotView.h"interface HSNoScreenShotView ()property (nonatomic,strong) UITextField *textField; pro…