rust学习十六.3、并发-线程之间共享数据

线程之间共享内存数据,即go中部分人所憎恶的方式!

然而,这个方式并非没有其优点,否则操作系统也不提供这种实现方式。

 

闲言少序,上正文!

一、概述

 * 1.当我们大谈基于信道通信时,应该指的是应用级别。如果是操作系统,应该还是会有多种措施的,否则rust的源头在哪里来着。
 * 2.rust共享内存,主要借助于Arc指针实现。所谓Arc,是Atomically Reference Counted的缩写,即原子引用计数。 它和Rc的主要
 *   区别在于,Arc允许多线程同时访问,而Rc不允许。
 * 3.此外还需要借助于Mutex(mutual exclusion 互相排斥之意),即互斥锁,来保证同一时刻只有一个线程可以访问共享资源。
 * 4.Mutex的主要方法有lock(),lock用于获取锁,如果成功返回一个MutexGuard智能指针。获取锁的时候,会阻塞线程
 * 5.MutexGuard智能指针实现了Deref和Drop,因此可以被当作原始数据的引用,同时Drop确保了当MutexGuard离开作用域时,锁会被释放。
 * 6.Mutex 是一个类似RefCell的智能指针,都提供了内部可变性
 * 7.Mutex<T> 也有造成 死锁(deadlock)的风险:当某个操作需要锁住两个资源,而两个线程分别持有两个资源的其中一个锁时,它们会永远相互等待
 * 8.对于简单的数值运算,标准库中 std::sync::atomic 模块 提供了比 Mutex<T> 更简单的类型。针对基本类型,这些类型提供了安全、并发、原子的操作
 *   例如 AtomicI32
 
Mutex的结构代码
复制代码
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "Mutex")]
pub struct Mutex<T: ?Sized> {
    inner: sys::Mutex,
    poison: poison::Flag,
    data: UnsafeCell<T>,
}
复制代码
 这个sys::Mutex是一个原子:
pub struct Mutex {
    futex: Atomic,
}
 用于持有锁position(位置标记/占有标记)是一个原子布尔,用于表示是否被占有data就是原始的数据了。
 Mutex最常用的方法lock用于获得锁,并返回如下:
复制代码
 #[stable(feature = "rust1", since = "1.0.0")]
    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
        unsafe {
            self.inner.lock();
            MutexGuard::new(self)
        }
    }
复制代码
 这段代码的大概意思:调用操作系统的内部锁,返回一个新的MutexGuard。完整的结果就是包含一个锁和一个结果 

二、示例-来自于书本的

复制代码
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 c = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
            println!("线程{:?}正在执行,得到{}", thread::current().id(),*num);
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}
复制代码

借用Arc指针(多线程使用的,可以共享数据)和Mutex锁实现简单的数字自增游戏! 很像餐桌上大伙共享一个公勺,有了公勺你要干什么都可以,但一个时刻只有一把公勺!

 

三、示例-特意构建的死锁

根据书本的要求,我特意构建一个死锁的代码。

发生死锁的最主要原因是互相等待对方持有的资源,好比劫匪持有人质,警察要人质,常常会陷入死循环!直到一方崩溃

复制代码
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
    let 人质 = Arc::new(Mutex::new(5));    
    let 自由 = Arc::new(Mutex::new(10));

    let h0=Arc::clone(&人质);
    let w0=Arc::clone(&自由);
    let 劫匪=thread::spawn(move || {      
        //线程0先获得人质资源,然后等待一段时间再获得自由资源。
        let mut _人质 = h0.lock().unwrap();        
        thread::sleep(Duration::from_millis(10));
        println!("我已经持有人质,快点让路!");
        let mut _自由 = w0.lock().unwrap();
        let 矛盾文学奖 = (*_人质) * (*_自由);
        println!("线程{:?}正在执行,得到{}", thread::current().id(), 矛盾文学奖);
    });

    let h1=Arc::clone(&人质);
    let w1=Arc::clone(&自由);
    let 警察=thread::spawn(move || {
        //线程1先获得的自由资源,然后等待一段时间再获得人质资源。
        let mut _自由 = w1.lock().unwrap();
        thread::sleep(Duration::from_millis(10));
        println!("赶紧放了人质,否则爆你狗头!");
        let mut _人质 = h1.lock().unwrap();        
        let 矛盾文学奖 = (*_人质) * (*_自由);
        println!("线程{:?}正在执行,得到{}", thread::current().id(), 矛盾文学奖);
    });

    劫匪.join().unwrap();
    警察.join().unwrap();
    
    println!("众人在围观....");
    let  _人质 = 人质.lock().unwrap();
    let  _自由 = 自由.lock().unwrap();
    let 矛盾文学奖 = (*_人质) * (*_自由);
    println!("Result: {}", 矛盾文学奖);
}
复制代码

 

四、Send和Sync特质

 * 1.Send 标记 trait 表明实现了 Send 的类型值的所有权可以在线程间传送。几乎所有的 Rust 类型都是Send 的,
 *   不过有一些例外,包括 Rc<T>
 * 2.任何完全由 Send 的类型组成的类型也会自动被标记为 Send。
 *   几乎所有基本类型都是 Send 的,除了第二十章将会讨论的裸指针(raw pointer)
 * 3.Sync 标记 trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用
 * 4.类似于 Send 的情况,基本类型是 Sync 的,完全由 Sync 的类型组成的类型也是 Sync 的
 * 5.通常并不需要手动实现 Send 和 Sync trait,因为由 Send 和 Sync 的类型组成的类型,
 *    自动就是 Send 和 Sync 的。因为它们是标记 trait,甚至都不需要实现任何方法。它们只是用来加强并发相关的不可变性的
 * 6.手动实现这些标记 trait 涉及到编写不安全的 Rust 代码

看看Arc的部分源码:

复制代码
#[cfg_attr(not(test), rustc_diagnostic_item = "Arc")]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_insignificant_dtor]
pub struct Arc<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
> {
    ptr: NonNull<ArcInner<T>>,
    phantom: PhantomData<ArcInner<T>>,
    alloc: A,
}

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Sync + Send, A: Allocator + Send> Send for Arc<T, A> {}
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Sync + Send, A: Allocator + Sync> Sync for Arc<T, A> {}
复制代码

 

这里的实现,并没有代码,包括Send和Sync,如作者言!

因此,顺便提一下,rust的trait不是java之类的接口,它具有更多的功能

同时,这也意味着,许多功能是编译器实现的。

五、小结

  1. rust使用Arc替代Rc指针,以便在多线程下的共有安全
  2. rust使用Mutex互斥锁,保证独享资源
  3. rust使用sync和send特质来保证两件事情:sync保证多个线程之间安全引用,send则保证多个线程之间的所有权安全传送!
  4. 正确编写代码,避免竞争死锁
  5. 避免死锁,必须遵循一些原则,例如
    • 尽量减少一个线程同时持有多个锁的情况。如果一个线程确实需要持有多个锁,应该确保所有锁都按照相同的顺序被获取和释放
    • 使用尝试锁(TryLock)
    • 设置锁超时
    • 检测和恢复死锁

       避免死锁的方法,有多种,这里只能列出其中一二比较简单有效的实践。

 

posted @   正在战斗中  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek-R1本地部署如何选择适合你的版本?看这里
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)
· 我们是如何解决abp身上的几个痛点
· 普通人也能轻松掌握的20个DeepSeek高频提示词(2025版)
点击右上角即可分享
微信分享提示