Sync trait 是 Send trait 的严格子集吗?什么实现不发送同步?

Is the Sync trait a strict subset of the Send trait; what implements Sync without Send?

“Rust 编程,第 2 版” 作者 Jim Blandy、Jason Orendorff、Leonora F.S。廷德尔 在第 520 页上有一个图表显示发送和同步与重叠的圆圈,同步完全包含在发送中。

这让我相信所有实现 Sync 的东西也必须实现 Send,但是第 561 页的这个例子和我看到的所有东西总是分别指定它们,

type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>

为什么如果实现Sync的东西100%也是Send,Sync不是Send的子trait吗?为什么 trait bounds 需要同时指定两者?为什么人们同时标记两者。是否有任何用例可以同步并且 发送?在什么情况下,您可以与另一个线程共享一个可变引用,但不向另一个线程授予所有权?

这本书似乎有误。 SendSync 之间的唯一关系是 TSync 当且仅当 &TSend(这是有道理的,因为“同步“跨线程实际上只是能够跨线程共享对它的引用)。事实上,标准库中甚至有一种类型是 Sync 而不是 SendMutexGuard。原因是当尝试从锁定它的线程以外的线程解锁互斥量时,底层实现会导致未定义的行为。

有可能实现 Sync 而不是 Send 的结构:


struct Foo(*const ());
unsafe impl Sync for Foo {}

fn main() {
    // Note: static just for demo using standard library threads.
    // Can use borrowed thread libraries with this too, where the lifetime isn't static.
    let static_foo_ref = Box::leak(Box::new(Foo(std::ptr::null()))) as &'static Foo;
    
    std::thread::spawn(move || {
        println!("Borrow thread, foo ptr: {:?}", static_foo_ref.0);
    }).join().unwrap();
    
    // Doesn't work
    /*let foo_owned = Foo(std::ptr::null());
    std::thread::spawn(move || {
        println!("Owned thread, foo ptr: {:?}", foo_owned.0);
    }).join().unwrap();*/
}

当类型实现 Send 特征时,您可以执行以下操作:

  1. 从创建它的线程以外的线程可变地访问该值。这包括删除它。
  2. 从创建它的线程以外的线程不可变地访问该值。 (但不一定是并行的。)

当类型实现 Sync 特征时,您可以执行以下操作:

  1. 不可变地从多个并行线程访问值。

“定义”T: Sync 含义的另一种方法就是将 &T 定义为 Send 是否安全。这是有道理的,因为可以复制 &T,因此制作副本并将它们发送到不同的线程将允许并行不可变访问。


如果值是 Send + !Sync,则可以从任何线程以任何方式访问它,但同时只能从一个线程访问,即使访问是不可变的。

如果一个值是 !Send + Sync,那么它可以被任何线程和多个线程并行访问。但是,可变访问必须发生在创建它的线程上。


一些示例:

  • MutexGuard - 在另一个线程上销毁 MutexGuard 是不合理的,所以它不能是 Send。但是,如果可以从多个并行线程不可变地访问内部值,那么这种不可变访问在 MutexGuard 本身上也是安全的。
  • SyncWrapper - 对 SyncWrapper<T> 的不可变引用根本不允许您执行任何操作,因此它始终是 Sync 是安全的。 (链接的 crate 要求内部值为 Send,但这比必要的更严格)
  • &T - 由于可以复制不可变引用,因此将一个引用发送到另一个线程的能力将使您可以从多个线程并行执行不可变引用。因此,如果 TSync,则 &T 只能是 SendT 不需要成为 Send,因为 &T 不允许可变访问。
  • &mut T - 可变引用无法复制,因此将它们发送到其他线程不允许并行访问多个线程,因此 &mut T 甚至可以是 Send如果 T 不是 Sync。当然,T一定还是Send.
  • Arc<T> - 这主要表现得像 &T。它可以被克隆,因此将它发送到其他线程需要 T: Sync。然而,它 需要 T: Send 因为最后一个 Arc<T> 可能会被放置在与创建 T 不同的线程上,你不能这样做没有 Send.
  • RefCell<T> - 这种类型永远不会是 Sync 因为你只能用一个不可变的引用来修改里面的值,如果你可以从多个线程来做这将是一场数据竞争平行线。 RefCell<T>Send 没有问题,前提是 T 是。
  • Rc<T> - 如果您有两个相同 Rc<T> 的克隆,那么从不同线程并行访问它们将是一场数据竞赛。这排除了 SendSync,因为它们都允许来自其他线程的不可变访问,并且其他线程可以使用它来远程调用 .clone() 并获得 Rc<T> 在另一个线程上。