I was recently confused by a compiler error while writing a multi-threaded program in Rust.
Problem
In short, I have a structure with an internal Rc
value, as follows.
Obviously, Rc
is not thread-safe, so it is protected by a lock and passed between threads with Arc
, so the following code is written.
However, the compiler’s throws an exception.
|
|
Simply put, Rc
in A
does not implement Send
, but this makes people confused, I add locks so that things that are not thread-safe can be shared between threads, but why do I need to implement Send
?
Reconceptualizing Sync and Send
Sync
and Send
are very important foundational concepts in Rust multithreading.
Sync
means that sharing between threads is safe.Send
means that passing to another thread is safe.
Sync
indicates that different threads can use the same object at the same time, for example, by reading the same constant at the same time. For a lock Mutex
, the Sync
trait is satisfied because several threads can execute lock()
on the same lock at the same time, except that the internal mechanism of the lock will only allow only one thread to obtain the lock at the same time, but this does not matter, its external performance is satisfied. So for variables, a layer of Mutex
is usually sufficient to allow them to be shared between threads.
Send
means that ownership can be passed between threads, and different threads can use the same object at different times. Thread A can create an object and then pass (send) it to thread B. Because of the ownership mechanism, thread B can then use the object and A cannot because ownership has been passed to B. However, if the object implements Clone
, then a copy can be made to another thread. The Mutex
does not implement the Clone
trait, so it is common to use Arc<Mutex>
to allow multiple threads to have the same lock at the same time.
It is worth mentioning that
Send
andSync
are flagged traits, meaning that they do not require any method implementation, and whether a structure satisfies them is manually flagged, not determined by the compiler, except for the condition that all members satisfy the trait, which is inferred from the fact that the structure also satisfies the trait. unsafe code, which cannot be protected by the compiler at implementation time and needs to be implemented carefully to ensure safety.
So it is easy to see that since it is safe to use at the same time (Sync
), it must also be safe to use at different times (Send
), after all, it cannot be passed to other threads, so how can several threads use it at the same time? So almost no object will be satisfied with Sync
and not Send
.
Back to the question
Let’s take a look at the definition in Mutex
.
You can see that Mutex<T>
can indeed add a Sync
trait to all types of T
, but only if T
satisfies Send
. Why? Because Mutex
must satisfy the Send
trait if it wants to implement Sync
, and then it also passes ownership of T
, so T
needs to satisfy Send
as well.
The fundamental problem is that Rc
doesn’t satisfy Send
, so Mutex
can’t implement Send
, which then creates a series of unsatisfiable problems that eventually lead to the failure to pass between threads.
Why doesn’t Rc
satisfy Send
? Send
means that it is safe to pass between threads, but for Rc
, if I have multiple copies of Rc
in the current thread and pass one to another thread, then multiple threads own the object, but the reference counting operation in Rc
is not thread-safe, so Rc
does not satisfy Send
, which means that Rc
can only be passed to one thread during its entire can only be owned by one thread for its entire life cycle.
So why doesn’t Rc
work even if it is locked with Mutex<Rc>
so that multiple threads can’t operate on it at the same time? The ostensible reason is that Mutex
requires T
to implement Send
. To take this a step further, imagine that you can make a copy of Rc
after obtaining the lock, and then take this copy out of the lock’s scope, so that several threads can modify the reference count at the same time, and the lock you’re envisioning is null and void, and Rust successfully saves you from that.
Hmm? You said
Mutex<Arc>
would also have this problem? TheArc
copy is thread-safe and doesn’t cause problems.What? You’re saying you can make a copy and have multiple threads modify it at the same time? A copy of
Arc
is brought out from the lock, butArc
doesn’t give you a mut ref, and there is no way to modify it. Really want to modify it? Put aMutex
on it.
Summary
The fundamental problem is that Rc
is not safe to pass between threads, so you can just replace it with Mutex<Arc>
.