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
X
is somehow related to multi/shared-ownership.
Rc
has a problem with this, since having one Rc
allows you to create more owned Rc
'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 that Rc
at 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 an Rc
, there would be only one counter really, and access into it would not be synchronized.
Arc
may work better. At least it's owner's counter is capable of dealing with the situation mentioned above. So in that regard, Arc
is ok to allow Send
'ing. But only if the inner type is both Send
and Sync
. For example, an Arc<Rc>
is still problematic - remembering that Rc
forbids Send
(!Send
) - because multiple threads having their own owned clone of that Arc<Rc>
could still invoke the Rc
's own "multi-thread" problems - the Arc
itself can't protect the threads from doing that. The other requirement of Arc<T>
, to being Send
, also requiring T
to be Sync
is not a big of a deal, because if a type is already forbidding Send
'ing, it will likely also be forbidding Sync
'ing.
- So if some type forbids
Send
ing, 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
&X
allows interior mutability, and you'd want to forbid Sync
if you want to prevent multiple threads having &X
.
- So if
X
has a problem with Send
ing, it will basically also have a problem with Sync
ing.
- It's also problematic for
Cell
- which doesn't actually forbids Send
ing. Since Cell
allows interior mutation by only having an &Cell
, and that mutation access doesn't guarantee anything in a multithread situation, it must forbid Sync
ing - that is, the situation of multiple threads having &Cell
must not be allowed (in general). Regarding it being Send
, an owned Cell
can still be moved into another thread, as long as there won't be &Cell
's anywhere else.
Mutex
may work better. It also allows interior mutation, and in which case it knows how to deal when many threads are trying to do it - the Mutex
will only require that nothing inside of it forbids Send
'ing - otherwise, it's the same problem that Arc
would have to deal with. All being good, the Mutex
is both Send
and Sync
.
- This is not a practical example, but a curious note: if we have a
Mutex<Cell>
(which is redundant, but oh well), where Cell
itself forbids Sync
, the Mutex
is able to deal with that problem, and still be (or "re-allow") Sync
. This is because, once a thread got access into that Cell
, we known it won't have to deal with other threads still trying to access others &Cell
at the same time, since the Mutex
will 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 Sync
ing while not particularly forbidding Send
ing, 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:
I don't know types that are !Send
and Sync
;