rust的Sync和Send对比

std::marker::Sync

可以安全地在线程之间共享引用的类型(it is safe to share references between threads)。
当编译器认为合适时(compiler determines it’s appropriate),此trait会自动实现。

精确的定义(precise definition ):

impl<T> Send for &T
where
    T: Sync + ?Sized,

换句话说,如果在线程之间传递 &T 引用时,不能存在任何未定义行为(包括数据争用)

像 u8 和 f64 这样的基本类型(primitive types)都是 Sync 的,包含它们的简单聚合类型也是如此(so are simple aggregate types containing them),比如元组、结构体和枚举。
基本Sync类型的更多示例包括“immutable”类型,例如 &T,以及具有简单继承可变性的类型(those with simple inherited mutability),例如 Box、Vec 和大多数其他集合类型。
(通用参数需要Sync,才能使其容器Sync)
(Generic parameters need to be Sync for their container to be Sync.)

该定义的一个有点令人惊讶的结果(somewhat surprising consequence)是 &mut T is Sync if T is Sync,尽管看起来可能会提供unsynchronized的突变(provide unsynchronized mutation)。
技巧在于共享引用(shared reference)后面的可变引用(mutable reference)(即 & &mut T)变为只读,as if it were a & &T(就像它是 & &T 一样)。因此不存在数据争用的风险。

因为 Send trait 要求类型 T 是线程安全的,而 Sync trait 要求引用 &T 在多线程环境中是安全的。

Sync 和 Send如何与引用相关(how Sync and Send relate to referencing)的简短概述:

  • &T is Send if and only if T is Sync
  • &mut T is Send if and only if T is Send
  • &T and &mut T are Sync if and only if T is Sync
use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    // 创建一个互斥锁(Mutex)
    // Creates a new mutex in an unlocked state ready for use
    // 类型:alloc::sync::Arc>
    let mutex = Arc::new(Mutex::new(0));

    // 克隆互斥锁的 Arc 指针,以便可以在多个线程中共享
    let mutex1 = Arc::clone(&mutex);
    let mutex2 = Arc::clone(&mutex);

    // 创建一个新的线程,修改互斥锁中的值
    let thread1 = thread::spawn(move || {
        let mut data = mutex1.lock().unwrap();
        *data += 1;
    });

    // 创建另一个新的线程,修改互斥锁中的值
    let thread2 = thread::spawn(move || {
        let mut data = mutex2.lock().unwrap();
        *data += 2;
    });

    // 等待线程结束
    thread1.join().unwrap();
    thread2.join().unwrap();

    // 打印最终互斥锁的值
    let data = mutex.lock().unwrap();
    println!("Final value: {}", *data);
}
impl<T> Sync for Arc<T>
where
    T: Sync + Send + ?Sized,

impl<T: ?Sized + Send> Send for Mutex<T>
impl<T: ?Sized + Send> Sync for Mutex<T>

以上文为例

impl Send for i32

所以
Mutex 具有 Send、Sync
Arc 具有 Sync

non-Sync 类型是那些以非线程安全形式具有“内部可变性”的类型(have “interior mutability” in a non-thread-safe form),例如 Cell 和 RefCell。即使通过不可变的共享引用(an immutable, shared reference),这些类型也允许更改其内容(allow for mutation of their contents)。例如,Cell 上的 set 方法采用 &self,因此它只需要共享引用 &Cell。该方法不进行同步(performs no synchronization),因此Cell不是Sync的

non-Sync 类型的另一个示例是引用计数指针(reference-counting pointer) Rc。给定任何引用 &Rc,您可以克隆一个新的 Rc,以非原子方式修改引用计数(modify the reference counts in a non-atomic way)。

对于确实需要线程安全的内部可变性的情况(thread-safe interior mutability),Rust 提供了原子数据类型(atomic data types),以及通过sync::Mutex 和sync::RwLock 进行显式锁(explicit locking)。这些类型确保任何突变都不会导致数据争用(any mutation cannot cause data races),因此这些类型是Sync的。同样,sync::Arc 提供了 Rc 的线程安全类似物(a thread-safe analogue of Rc)。

任何具有内部可变性的类型(Any types with interior mutability)还必须使用 cell::UnsafeCell wrapper来包装可以通过共享引用进行变异的值(can be mutated through a shared reference)。不这样做是未定义的行为。例如,从 &T 转换为 &mut T 是无效的。

有关同步的更多详细信息,请参阅 https://doc.rust-lang.org/nomicon/send-and-sync.html

std::marker::Send

可以跨线程传输的类型(Types that can be transferred across thread boundaries)。
当编译器认为合适时,此特征会自动实现。

non-Send的一个示例是引用计数指针 rc::Rc(reference-counting pointer)。如果两个线程尝试克隆指向相同引用计数值的 Rc,它们可能会尝试同时更新引用计数,这是未定义的行为,因为 Rc 不使用原子操作。它的表兄弟(cousin)sync::Arc使用原子操作(产生一些开销,incur some overhead),因此是Send的。

区别

Sync trait用于标记类型是线程安全的,可以安全地在多个线程中共享
Send trait用于标记类型可以安全地在线程之间转移所有权

如果一个类型实现了Sync trait,那么它就可以被多个线程同时拥有的&T引用所访问,其中T是该类型的实例
如果一个类型实现了Send trait,那么它可以安全地在不同线程之间转移所有权。

附录

Arc的Clone

impl<T> Clone for Arc<T>
where
    T: ?Sized,

fn clone(&self) -> Arc<T>

克隆 Arc 指针(Makes a clone of the Arc pointer)。
这会创建另一个指向同一分配(same allocation)的指针,从而增加强引用计数(strong reference count)

use std::sync::Arc;
let five = Arc::new(5);
let _ = Arc::clone(&five);

你可能感兴趣的:(rust,rust,开发语言,后端)