了解发送特征

rus*_*sty 25 rust

我正试图围绕Send + Sync特征进行思考。我得到了背后的直觉Sync- 这是传统的线程安全(如C++)。该对象执行必要的锁定(如果需要,内部可变性),因此线程可以安全地访问它。

Send部分有点不清楚。我明白为什么事情喜欢RcSend只是-对象可以给不同的线程,但非原子操作使线程不安全。

  1. 背后的直觉是Send什么?这是否意味着该对象可以被复制/移动到另一个线程上下文中,并且在复制/移动后继续有效?

  2. Sync但没有Send”的任何示例场景都会真正有帮助。还请指出这种情况下的任何 Rust 库(不过我发现了几个相反的库)

对于(2),我发现一些线程使用带有指向堆栈/线程本地存储上的数据的指针的结构作为示例。但无论如何这些都是不安全的(同步或其他方式)。

小智 36

Sync允许一个对象同时被两个线程A和B使用。这对于非可变对象来说很简单,但是突变需要同步(按顺序执行,所有线程都看到相同的顺序)。这通常是使用Mutexor来完成的RwLock,它允许一个线程继续,而其他线程必须等待。通过强制执行共享的更改顺序,这些类型可以将非Sync对象转变为Sync对象。制作对象的另一种机制Sync是使用原子类型,它本质上是Sync原语。

Send允许两个线程 A 和 B 在不同时间使用一个对象。线程 A 可以创建并使用一个对象,然后将其发送给线程 B,因此线程 B 可以使用该对象,而线程 A 则不能。Rust 所有权模型可用于强制执行这种非重叠使用。因此,所有权模型是 Rust 线​​程安全的重要组成部分,这可能是与其他语言相比不太直观的Send原因。SendSync

使用上面的定义,应该很清楚为什么很少有类型的示例是 ,Sync但不是Send。如果一个对象可以同时被两个线程安全地使用(Sync),那么它也可以被两个线程在不同的时间安全地使用(Send)。因此,Sync通常意味着Send.

如果可以保证在不同时间使用,大多数对象都可以被不同线程安全地使用。因此,大多数类型都是Send.

Rc是一个例外。它没有实施SendRc允许数据有多个所有者。如果线程 A 中的一个所有者可以将 对象发送Rc给另一个线程,从而将所有权授予线程 B,则线程 A 中的其他所有者可能仍然可以使用该对象。由于引用计数是非原子修改的,因此两个线程上的计数值可能会不同步,并且一个线程可能会删除指向的值,而另一个线程中存在所有者。

ArcRc使用原子类型作为引用计数的。因此,它可以被多个线程使用,而计数不会不同步。Arc如果指向的数据是Sync,则整个对象是Sync。如果数据不是Sync(例如可变类型),则可以Sync使用Mutex. Arc<Mutex<T>>因此,多线程 Rust 代码中的类型激增。


Thi*_*ado 13

全面的

SendSync存在是为了帮助考虑涉及多个线程时的类型。在单线程世界中,没有必要SendSync没有存在。

不要总是想着允许你做某事,或者赋予你做某事的权力,这也Send可能会有所帮助相反,请考虑将和作为禁止阻止您执行多线程有问题的操作的方法。Sync!Send!Sync

Send对于和的定义Sync

如果某种类型XSend,那么如果您有一个拥有的X,您可以将其移动到另一个线程中。

  • X如果与多重/共享所有权有某种关系,这可能会出现问题。
  • Rc这样做有一个问题,因为拥有一个Rc允许您创建更多拥有的Rc(通过克隆它),但您不希望其中任何一个传递到其他线程。问题在于,许多线程可能会Rc同时创建更多克隆,并且其中所有者的计数器在多线程情况下不能很好地工作 - 因为即使每个线程都拥有一个Rc,也会有实际上只有一个计数器,并且对它的访问不会同步。
  • Arc可能会工作得更好。至少它的主人柜台有能力应对上述情况。所以在这方面,Arc允许Send“ing”是可以的。但前提是内部类型是Sendand Sync。例如, anArc<Rc>仍然是有问题的 - 请记住Rc禁止Send( !Send) - 因为多个线程拥有自己拥有的克隆,Arc<Rc>仍然可能引发Rc自己的“多线程”问题 - 本身Arc无法保护线程不这样做。的另一个要求Arc<T>,对于存在Send,也要求TSync,这并不是什么大不了的事情,因为如果一个类型已经禁止Send'ing,它也可能禁止Sync'ing。
  • 因此,如果某种类型禁止Sending,那么无论您尝试围绕它包装什么其他类型,您都无法将其“发送”到另一个线程中。

如果某种类型XSync,那么如果多个线程碰巧以某种方式拥有&Xeach,它们都可以安全地使用该类型&X

  • &X如果允许内部可变性,这是有问题的,并且Sync如果您想防止多个线程具有&X.
  • 所以如果ingX有问题Send,基本上 ing 也会有问题Sync
  • 这也是有问题的Cell- 它实际上并不禁止Sending。由于Cell仅通过具有 来允许内部突变&Cell,并且该突变访问不能保证多线程情况下的任何内容,因此它必须禁止Syncing - 也就是说,&Cell不得允许多个线程具有的情况(通常)。关于它Send,一个拥有的Cell仍然可以移动到另一个线程中,只要&Cell其他地方不存在。
  • Mutex可能会工作得更好。它还允许内部突变,在这种情况下,当许多线程尝试执行此操作时,它知道如何处理 -Mutex只需要它内部没有任何内容禁止'ing - 否则,这是必须处理的Send相同问题。Arc一切都很好,MutexSendSync
  • 这不是一个实际的例子,而是一个奇怪的注释:如果我们有一个Mutex<Cell>(这是多余的,但是哦好吧),它Cell本身禁止Sync,那么它Mutex能够处理这个问题,并且仍然是(或“重新允许”)Sync。这是因为,一旦一个线程访问了 that Cell,我们就知道它不必处理&Cell同时仍尝试访问其他线程的其他线程,因为该线程Mutex将被锁定并防止这种情况发生。

在多线程中改变一个值

理论上你可以在Mutex线程之间共享一个!
如果您尝试简单地移动一个拥有的Mutex,您将完成它,但这没有用,因为您希望多个线程同时对其进行一些访问。
因为它是Sync,所以你可以&Mutex在线程之间共享一个,并且它的锁定方法确实只需要一个&Mutex。但是尝试这样做是有问题的,比方说:您在main线程中,然后创建 a Mutex,然后创建对它的引用 a &Mutex,然后创建另一个Z尝试将其传递到的线程&Mutex。问题是该对象Mutex只有一个所有者,并且位于main线程内部。如果由于某种原因,线程的Z寿命超过了main线程的寿命,那么该线程&Mutex就会悬空。因此,即使Syncin 并Mutex没有特别禁止您&Mutex在线程之间发送/共享,出于生命周期的原因,您也可能不会以这种方式完成它。Arc来救援! Arc将摆脱终生的问题。它不是由特定线程中的特定范围拥有,而是可以由多线程拥有。因此,使用 anArc<Mutex>将允许一个值被共同拥有和共享,并提供许多线程之间的内部可变性。总之,Mutex本身重新允许Syncing,但没有特别禁止Sending,并且Arc在提供共享所有权(避免生命周期问题)时也没有特别禁止。

类型的小列表

类型 和Send,Sync是那些没有特别禁止的类型:

  • 基元, Arc, Mutex- 取决于内部类型

Send和的类型!Sync是那些提供(多线程不同步)内部可变性的类型:

  • Cell, RefCell- 取决于内部类型

!Send和 的类型!Sync是提供(多线程不同步)共同所有权的类型:

  • Rc

我不知道!Sendand的类型Sync


Frx*_*rem 8

Send意味着一种类型可以安全地从一个线程移动到另一个线程。如果相同的类型也实现了Copy,这也意味着 从一个线程复制到另一个线程是安全的。

Sync意味着同时从多个线程引用一个类型是安全的。具体而言,&TSend和可移动/复制到另一个线程,如果TSync

因此,SendSync捕获线程安全的两个不同方面:

  • Send类型只能由单个线程拥有,因为它们不能移动或复制到其他线程。
  • Sync类型只能由单个线程在任何时候使用,因为它们的引用不能移动或复制到其他线程。如果它们实现了,它们仍然可以在线程之间移动Send

它很少有意义的有Sync没有Send,因为能够使用一种不同的线程通常意味着线程之间移动所有权也应该是可能的。尽管它们在技术上有所不同,因此可以想象某些类型可以Sync但不能Send

大多数拥有数据的类型将是Send,因为很少有数据不能从一个线程移动到另一个线程(并且之后不能从原始线程访问)的情况。一些常见的例外:

  • 原始指针从不Send也不是Sync
  • 在没有线程同步的情况下共享数据所有权的类型(例如Rc)。
  • 借用数据的类型不是Sync.
  • 来自外部库或非线程安全的操作系统的类型。

  • `Box&lt;T&gt;` 本质上可以被视为一个 `T`。它代表“T”的所有权,如果“T:Send”/“T:Sync”,还将实现“Send”/“Sync”特征。Box&lt;T&gt; 在语义上所做的唯一事情就是将对象移动到堆中并为您提供指向它的指针。不过这个指针很特殊,因为指针(我将其称为盒子)现在拥有该对象。在大多数情况下,您应该将 `Box&lt;T&gt;` 视为与 `T` 相同。 (2认同)