Rust中的并发性:Sync 和 Send Traits

在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时,编译器会直接拒绝编译。那么它是靠什么魔法做到的呢?

这就不得不谈 Send 和 Sync 这两个标记 trait 了,实现 Send 的类型可以在多线程间转移所有权,实现 Sync 的类型可以在多线程间共享引用。但它们内部都是没有任何方法声明以及方法体的,二者仅仅是作为一个类型约束的标记信息提供给编译器,帮助编译器拒绝线程不安全的代码。

定义:

pub unsafe auto trait Send { }

pub unsafe auto trait Sync { }

本文将深入探讨

Sync



Send

traits,了解为什么某些类型实现这些 traits,而另一些则没有,并讨论 Rust 中并发编程的最佳实践。

The Sync Trait


Sync

trait 表示一个类型可以安全地被多个线程同时访问。这里的访问指的是只读共享安全。Rust 中几乎所有的原始类型都实现了

Sync

trait

例如:

let x = 5; // i32 is Sync


i32

类型实现了

Sync

,所以在线程间共享

i32

值是安全的。

另一方面,提供内部可变性的类型(内部可变性指的是在拥有不可变引用的时候,依然可以获取到其内部成员的可变引用,进而对其数据进行修改。),如

Mutex<T>

,其中 T 未实现

Sync

trait。

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

因为

Mutex

使用锁来保护对内部数据的访问,如果多个线程同时访问它,可能会导致数据竞争或死锁。

举例来说:

use std::sync::Mutex;

let m = Mutex::new(5); //Mutex<i32> is not Sync


Mutex<i32>

类型没有实现

Sync

,所以跨线程共享是不安全的。

为在多个线程安全地访问非

Sync

类型(如

Mutex<i32>

),我们必须使用适当的同步操作,如获取锁,执行操作和释放锁,在本文后面看到使用互斥锁和其他线程安全类型的示例。

支持 Sync 的类型

Rust 中的 Sync trait 确保了对同一数据的多个引用(无论是可变的还是不可变的)可以安全地从多个线程并发访问。任何实现 Sync trait 的类型

T

都可以被认为是“线程安全”的。

Rust 中的 Sync 类型的一些例子是:

  • 原始类型,如

    i32



    bool



    char

    等。
  • 简单的聚合类型,如元组

    (i32, bool)
  • 原子类型,如

    AtomicBool

另一方面,非同步类型不能同时使用多个引用,因为这可能导致数据竞争。非同步类型的一些示例包括:


  • Mutex<i32>

    – 在访问内部 i32 之前需要锁定互斥体。

  • RefCell<i32>

    – 在访问内部值之前需要借用 RefCell。

  • Rc<i32>

    – 共享了内部 i32 的所有权,所以多个可变借用是不安全的。

非 Sync 类型多线程访问

Mutex

为在多个线程安全地访问非同步类型,我们需要使用同步原语,如互斥锁。若仅仅使用 Mutex 而不使用 Arc ,可使用像作用域线程(crossbeam),例如:

这里,我们使用

Mutex<i32>

来安全地从多个线程中修改和读取内部 String。

lock()

方法获取锁,阻止其他线程访问互斥体。

Atomic



AtomicU64

这样的原子类型也可以使用像

fetch_add()

这样的原子操作从多个线程安全地访问。例如:

总结

因此,总而言之,要在 Rust 中跨线程共享数据,数据必须:

  • 类型为

    Sync

    (原始/不可变类型)
  • 封装在互斥或原子类型中(Mutex、RwLock、Atomic*)
  • 使用像通道这样的消息传递技术来跨线程传递数据的所有权。

The Send Trait

Rust 中的

Send

trait 表示类型可以安全地跨线程边界传输。如果一个类型实现了

Send

,这意味着该类型的值的所有权可以在线程之间转移。

例如,像

i32



bool

这样的原始类型是

Send

因为它们在线程之间共享时没有任何内部引用或可变而导致问题:

然而,像

Rc<i32>

这样的类型未实现

Send

,因为它的引用计数在内部发生了变化,并且多个线程改变相同的引用计数可能会导致内存不安全:



Rc<T>

这样的非

Send

类型不能跨线程传输,但它们仍然可以在单个线程中使用。当线程需要共享一些数据时,非

Send

类型可以被包装在像

Arc<T>

这样的线程安全的包装器中,Arc

使用原子操作来管理引用计数,并允许内部类型在线程之间共享。

总结一下,关于

Send

的几个关键点是:

  • 类型

    Send

    可以在线程之间转移所有权


  • i32



    bool

    这样的原始类型是

    Send
  • 具有内部可变的类型(如

    Rc<T>

    )通常不是

    Send


  • Send

    类型仍然可以在单个线程中使用,或者在包装在像

    Arc<T>

    这样的线程安全的容器中时在线程之间共享
  • 跨线程传输非

    Send

    类型会导致未定义的行为和内存不安全

自定义实现 Sync 和 Send

要创建自定义类型

Sync



Send

,您只需实现类型的

Sync



Send

trait。

这里有一个 持有裸指针

*const u8



MyBox

结构体, 由于

只要复合类型中有一个成员不是 Send 或者 Sync,那么该类型也就不是 Send 或 Sync

。裸指针

*const u8

均未实现

Send



Sync Trait



MyBox

复合类型也不是

Send



Sync

若给 MyBox 实现了 Send 和 Sync 则借助 Arc 可在线程间传递和共享数据。当然建议自己不要轻易去实现 Sync 和 Send Trait ,一旦实现就要为被实现类型的线程安全性负责。这件事本来就是一件很难保证的事情。

有些类型是不可能生成

Sync



Send

的,因为它们包含非

Sync

/非

Send

类型或允许多线程的可变。例如,

Rc<T>

不能被设置为

Send

,因为引用计数需要被原子地更新,而

RefCell<T>

不能被设置为

Sync

,因为它的借用检查不是线程安全的。


同步/发送规则和最佳实践

重要的是要记住混合

Sync

/

Send

和非

Sync

/非

Send

类型的规则。一些需要遵守的关键规则:

类型必须是

Send

才能在线程之间移动。这意味着像

Rc<T>

这样的类型不能跨线程共享,因为它们不是

Send

  • 如果一个类型包含一个非

    Send

    类型,那么外部类型不能是

    Send

    。例如

    Option<Rc<i32>>

    不是

    Send

    ,因为

    Rc<i32>

    不是

    Send


  • Sync

    类型可以通过共享引用从多个线程并发使用。非

    Sync

    类型不能同时使用它们的值,并且一次只能在一个线程中可变。
  • 如果一个类型包含一个非

    Sync

    类型,那么外部类型不能是

    Sync

    。例如

    Mutex<Rc<i32>>

    不是

    Sync

    ,因为

    Rc<i32>

    不是

    Sync

并发 Rust 代码的一些最佳实践:

  • 尽可能避免可变。支持不可变的数据结构和逻辑。
  • 当需要修改时,使用同步原语(如

    Mutex<T>

    )来安全地从多个线程进行。
  • 使用消息传递在线程之间进行通信,而不是直接共享内存。这有助于避免数据竞争和未定义的行为。
  • 尽可能地限制为修改锁定数据的范围。持有锁太长时间会影响性能和吞吐量。
  • 根据它们是否实现

    Sync



    Send

    仔细选择类型。例如,在线程之间共享时,首选

    Arc<T>

    而不是

    Rc<T>

  • 使用

    atomic

    类型进行简单的并发访问原语类型。它们允许从多个线程访问而不加锁。

参考链接

Concurrency in Rust: The Sync and Send Traits | by Technocrat | CoderHack.com | Medium

[基于 Send 和 Sync 的线程安全 – Rust 语言圣经(Rust Course)](
https://course.rs/advance/concurrency-with-threads/send-sync.html
“基于 Send 和 Sync 的线程安全 – Rust 语言圣经(Rust Course “基于 Send 和 Sync 的线程安全 – Rust 语言圣经(Rust Course)”)”)

Rust 中的 Arc 和 Mutex|关键在于 — Arc and Mutex in Rust | It’s all about the bit

Rust 入门与实践

未经允许不得转载:大白鲨游戏网 » Rust中的并发性:Sync 和 Send Traits