'Understanding the Send trait
I am trying to wrap my head around Send + Sync traits. I get the intuition behind Sync - this is the traditional thread safety(like in C++). The object does the necessary locking(interior mutability if needed), so threads can safely access it.
But the Send part is bit unclear. I understand why things like Rc are Send only - the object can be given to a different thread, but non-atomic operations make it thread unsafe.
What is the intuition behind
Send? Does it mean the object can be copied/moved into another thread context, and continues to be valid after the copy/move?Any examples scenarios for "
Syncbut noSend" would really help. Please also point to any rust libraries for this case (I found several for the opposite though)
For (2), I found some threads which use structs with pointers to data on stack/thread local storage as examples. But these are unsafe anyways(Sync or otherwise).
Solution 1:[1]
Sync allows an object to to be used by two threads A and B at the same time. This is trivial for non-mutable objects, but mutations need to be synchronized (performed in sequence with the same order being seen by all threads). This is often done using a Mutex or RwLock which allows one thread to proceed while others must wait. By enforcing a shared order of changes, these types can turn a non-Sync object into a Sync object. Another mechanism for making objects Sync is to use atomic types, which are essentially Sync primitives.
Send allows an object to be used by two threads A and B at different times. Thread A can create and use an object, then send it to thread B, so thread B can use the object while thread A cannot. The Rust ownership model can be used to enforce this non-overlapping use. Hence the ownership model is an important part of Rust's Send thread safety, and may be the reason that Send is less intuitive than Sync when comparing with other languages.
Using the above definitions, it should be apparent why there are few examples of types that are Sync but not Send. If an object can be used safely by two threads at the same time (Sync) then it can be used safely by two threads at different times (Send). Hence, Sync usually implies Send. Any exception probably relates to Send's transfer of ownership between threads, which affects which thread runs the Drop handler and deallocates the value.
Most objects can be used safely by different threads if the uses can be guaranteed to be at different times. Hence, most types are Send.
Rc is an exception. It does not implement Send. Rc allows data to have multiple owners. If one owner in thread A could send the Rc to another thread, giving ownership to thread B, there could be other owners in thread A that can still use the object. Since the reference count is modified non-atomically, the value of the count on the two threads may get out of sync and one thread may drop the pointed-at value while there are owners in the other thread.
Arc is an Rc that uses an atomic type for the reference count. Hence it can be used by multiple threads without the count getting out of sync. If the data that the Arc points to is Sync, the entire object is Sync. If the data is not Sync (e.g. a mutable type), it can be made Sync using a Mutex. Hence the proliferation of Arc<Mutex<T>> types in multithreaded Rust code.
Solution 2:[2]
Send means that a type is safe to move from one thread to another. If the same type also implements Copy, this also means that it is safe to copy from one thread to another.
Sync means that a type is safe to reference from multiple threads at the same time. Specifically, that &T is Send and can be moved/copied to another thread if T is Sync.
So Send and Sync capture two different aspects of thread safety:
- Non-
Sendtypes can only ever be owned by a single thread, since they cannot be moved or copied to other threads. - Non-
Synctypes can only be used by a single thread at any single time, since their references cannot be moved or copied to other threads. They can still be moved between threads if they implementSend.
It rarely makes sense to have Sync without Send, as being able to use a type from different threads would usually mean that moving ownership between threads should also be possible. Although they are technically different, so it is conceivable that certain types can be Sync but not Send.
Most types that own data will be Send, as there are few cases where data can't be moved from one thread to another (and not be accessed from the original thread afterwards).
Some common exceptions:
- Raw pointers are never
SendnorSync. - Types that share ownership of data without thread synchronization (for instance
Rc). - Types that borrow data that is not
Sync. - Types from external libraries or the operating system that are not thread safe.
Solution 3:[3]
Overall
Send and Sync exist to help thinking about the types when many threads are involved. In a single thread world, there is no need for Send and Sync to exist.
It may help also to not always think about Send and Sync as allowing you to do something, or giving you power to do something. On the contrary, think about !Send and !Sync as ways of forbidding or preventing you of doing multi-thread problematic stuff.
For the definition of Send and Sync
If some type X is Send, then if you have an owned X, you can move it into another thread.
- This can be problematic if
Xis somehow related to multi/shared-ownership. Rchas a problem with this, since having oneRcallows you to create more ownedRc's (by cloning it), but you don't want any of those to pass into other threads. The problem is that many threads could be making more clones of thatRcat the same time, and the counter of the owners inside of it doesn't work well in that multi-thread situation - because even if each thread would own anRc, there would be only one counter really, and access into it would not be synchronized.Arcmay work better. At least it's owner's counter is capable of dealing with the situation mentioned above. So in that regard,Arcis ok to allowSend'ing. But only if the inner type is bothSendandSync. For example, anArc<Rc>is still problematic - remembering thatRcforbidsSend(!Send) - because multiple threads having their own owned clone of thatArc<Rc>could still invoke theRc's own "multi-thread" problems - theArcitself can't protect the threads from doing that. The other requirement ofArc<T>, to beingSend, also requiringTto beSyncis not a big of a deal, because if a type is already forbiddingSend'ing, it will likely also be forbiddingSync'ing.- So if some type forbids
Sending, then doesn't matter what other types you try wrapping around it, you won't be able to make it "sendable" into another thread.
If some type X is Sync, then if multiple threads happened to somehow have an &X each, they all can safely use that &X.
- This is problematic if
&Xallows interior mutability, and you'd want to forbidSyncif you want to prevent multiple threads having&X. - So if
Xhas a problem withSending, it will basically also have a problem withSyncing. - It's also problematic for
Cell- which doesn't actually forbidsSending. SinceCellallows interior mutation by only having an&Cell, and that mutation access doesn't guarantee anything in a multithread situation, it must forbidSyncing - that is, the situation of multiple threads having&Cellmust not be allowed (in general). Regarding it beingSend, an ownedCellcan still be moved into another thread, as long as there won't be&Cell's anywhere else. Mutexmay work better. It also allows interior mutation, and in which case it knows how to deal when many threads are trying to do it - theMutexwill only require that nothing inside of it forbidsSend'ing - otherwise, it's the same problem thatArcwould have to deal with. All being good, theMutexis bothSendandSync.- This is not a practical example, but a curious note: if we have a
Mutex<Cell>(which is redundant, but oh well), whereCellitself forbidsSync, theMutexis able to deal with that problem, and still be (or "re-allow")Sync. This is because, once a thread got access into thatCell, we known it won't have to deal with other threads still trying to access others&Cellat the same time, since theMutexwill be locked and preventing this from happening.
Mutate a value in multi-thread
In theory you could share a Mutex between threads!
If you try to simply move an owned Mutex, you will get it done, but this is of no use, since you'd want multiple threads having some access to it at the same time.
Since it's Sync, you're allowed to share a &Mutex between threads, and it's lock method indeed only requires a &Mutex.
But trying this is problematic, let's say: you're in the main thread, then you create a Mutex and then a reference to it, a &Mutex, and then create another thread Z which you try to pass the &Mutex into.
The problem is that the Mutex has only one owner, and that is inside the main thread. If for some reason the thread Z outlives the main thread, that &Mutex would be dangling. So even if the Sync in the Mutex doesn't particularly forbids you of sending/sharing &Mutex between threads, you'll likely not get it done in this way, for lifetime reasons. Arc to the rescue!
Arc will get rid of that lifetime problem. instead of it being owned by a particular scope in a particular thread, it can be multi-owned, by multi-threads.
So using an Arc<Mutex> will allow a value to be co-owned and shared, and offer interior mutability between many threads. In sum, the Mutex itself re-allows Syncing while not particularly forbidding Sending, and the Arc doesn't particularly forbids neither while offering shared ownership (avoiding lifetime problems).
Small list of types
Types that are Send and Sync, are those that don't particularly forbids neither:
- primitives,
Arc,Mutex- depending on the inner types
Types that are Send and !Sync, are those that offer (multithread unsync) interior mutability:
Cell,RefCell- depending on the inner type
Types that are !Send and !Sync, are those that offer (multithread unsync) co-ownership:
Rc
I don't know types that are !Send and Sync;
Solution 4:[4]
According to Rustonomicon: Send and Sync
A type is Send if it is safe to send it to another thread.
A type is Sync if it is safe to share between threads (T is Sync if and only if &T is Send).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | cibercitizen1 |
