前端开发可以做网站运营吗,wordpress上传权限,溜冰鞋 东莞网站建设,大连网站流量优化定制在并发的世界中#xff0c;最常见的并发安全问题就是数据竞争#xff0c;也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时#xff0c;编译器会直接拒绝编译。那么它是靠什么魔法做到的呢#xff1f;
这就不得不谈 Send 和 Sync 这…
在并发的世界中最常见的并发安全问题就是数据竞争也就是两个线程同时对一个变量进行读写操作。但当你在 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 值是安全的。
另一方面提供内部可变性的类型(内部可变性指的是在拥有不可变引用的时候依然可以获取到其内部成员的可变引用进而对其数据进行修改。)如 MutexT 其中 T 未实现 Sync trait。
#[stable(feature rust1, since 1.0.0)]
unsafe implT: ?Sized Send Send for MutexT {}
#[stable(feature rust1, since 1.0.0)]
unsafe implT: ?Sized Send Sync for MutexT {}
因为 Mutex 使用锁来保护对内部数据的访问如果多个线程同时访问它可能会导致数据竞争或死锁。
举例来说
use std::sync::Mutex;let m Mutex::new(5); //Mutexi32 is not Sync
Mutexi32 类型没有实现 Sync 所以跨线程共享是不安全的。
为在多个线程安全地访问非 Sync 类型如 Mutexi32 我们必须使用适当的同步操作如获取锁执行操作和释放锁在本文后面看到使用互斥锁和其他线程安全类型的示例。
支持 Sync 的类型
Rust 中的 Sync trait 确保了对同一数据的多个引用无论是可变的还是不可变的可以安全地从多个线程并发访问。任何实现 Sync trait 的类型 T 都可以被认为是“线程安全”的。
Rust 中的 Sync 类型的一些例子是
原始类型如 i32 、 bool 、 char 等。简单的聚合类型如元组 (i32, bool)原子类型如 AtomicBool
另一方面非同步类型不能同时使用多个引用因为这可能导致数据竞争。非同步类型的一些示例包括
Mutexi32 - 在访问内部 i32 之前需要锁定互斥体。RefCelli32 - 在访问内部值之前需要借用 RefCell。Rci32 - 共享了内部 i32 的所有权所以多个可变借用是不安全的。
非 Sync 类型多线程访问
Mutex
为在多个线程安全地访问非同步类型我们需要使用同步原语如互斥锁。若仅仅使用 Mutex 而不使用 Arc 可使用像作用域线程(crossbeam)例如 这里我们使用 Mutexi32 来安全地从多个线程中修改和读取内部 String。 lock() 方法获取锁阻止其他线程访问互斥体。
Atomic
像 AtomicU64 这样的原子类型也可以使用像 fetch_add() 这样的原子操作从多个线程安全地访问。例如 总结
因此总而言之要在 Rust 中跨线程共享数据数据必须
类型为 Sync 原始/不可变类型封装在互斥或原子类型中Mutex、RwLock、Atomic*使用像通道这样的消息传递技术来跨线程传递数据的所有权。
The Send Trait
Rust 中的 Send trait 表示类型可以安全地跨线程边界传输。如果一个类型实现了 Send 这意味着该类型的值的所有权可以在线程之间转移。
例如像 i32 和 bool 这样的原始类型是 Send 因为它们在线程之间共享时没有任何内部引用或可变而导致问题 然而像 Rci32 这样的类型未实现 Send 因为它的引用计数在内部发生了变化并且多个线程改变相同的引用计数可能会导致内存不安全 像 RcT 这样的非 Send 类型不能跨线程传输但它们仍然可以在单个线程中使用。当线程需要共享一些数据时非 Send 类型可以被包装在像 ArcT 这样的线程安全的包装器中Arc 使用原子操作来管理引用计数并允许内部类型在线程之间共享。
总结一下关于 Send 的几个关键点是
类型 Send 可以在线程之间转移所有权像 i32 和 bool 这样的原始类型是 Send具有内部可变的类型如 RcT 通常不是 Send非 Send 类型仍然可以在单个线程中使用或者在包装在像 ArcT 这样的线程安全的容器中时在线程之间共享跨线程传输非 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类型或允许多线程的可变。例如 RcT不能被设置为Send因为引用计数需要被原子地更新而RefCellT不能被设置为 Sync因为它的借用检查不是线程安全的。
同步/发送规则和最佳实践
重要的是要记住混合Sync/Send 和非Sync/非Send类型的规则。一些需要遵守的关键规则
类型必须是Send才能在线程之间移动。这意味着像RcT这样的类型不能跨线程共享因为它们不是Send 。
如果一个类型包含一个非Send类型那么外部类型不能是Send。例如OptionRci32不是 Send 因为Rci32不是Send 。Sync类型可以通过共享引用从多个线程并发使用。非Sync类型不能同时使用它们的值并且一次只能在一个线程中可变。如果一个类型包含一个非Sync类型那么外部类型不能是Sync。例如MutexRci32不是Sync 因为Rci32不是Sync 。
并发 Rust 代码的一些最佳实践
尽可能避免可变。支持不可变的数据结构和逻辑。当需要修改时使用同步原语如MutexT来安全地从多个线程进行。使用消息传递在线程之间进行通信而不是直接共享内存。这有助于避免数据竞争和未定义的行为。尽可能地限制为修改锁定数据的范围。持有锁太长时间会影响性能和吞吐量。根据它们是否实现Sync和Send仔细选择类型。例如在线程之间共享时首选ArcT而不是 RcT 。使用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 | Its all about the bit
Rust 入门与实践