In the Arc::make_mut
, the memory order of store
operation is Release
, see sync.rs - source. However, why does it need Release
?
pub fn make_mut(this: &mut Self) -> &mut T {
if this.inner().strong.compare_exchange(1, 0, Acquire, Relaxed).is_err() { // #1
// ...
} else if this.inner().weak.load(Relaxed) != 1{
// ...
}else{
this.inner().strong.store(1, Release); // #2
}
unsafe { Self::get_mut_unchecked(this) }
}
// Drop
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Arc<T, A> {
fn drop(&mut self) {
if self.inner().strong.fetch_sub(1, Release) != 1 {
return;
}
acquire!(self.inner().strong);
// deallocation
}
}
The first condition of if
at least guarantees that this object is unique when invoking the store operation at #2
. Either the deallocation of the Arc
object is sequence-after make_mut
or the deallocation occurs in another thread. In the latter case, the Arc
object(or copy) must be sent to another thread via synchronization facilities.
Moreover, the CAS
at #1
uses the Acquire
memory order, which guarantees that the other drops synchronize with #1
and #1
either sequenced before the drop that invokes deallocation at the current thread or synchronizes with the drop that invokes deallocation in another thread, which provides that all use of Arc
happen-before the deallocation. The figure could be:
//thread 1
use(a0);
drop(a0); // no deallocation, fetch_sub_release_0
//thread 2
use(a1);
drop(a1); // no deallocation, fetch_sub_release_1
// ...
// thread N:
make_mut(a_n); // CAS and store
// 1. either
// drop(a_n); deallocation here, fetch_sub_release_n
// or 2.
// send_to_other_thread(a_n/a_n.clone());
// another thread if send:
let a = receive();
drop(a); // deallocation occurs here, fetch_sub_release_final
If the deallocation occurs at thread N
, then the modification order should be
fetch_sub_release_0 < fetch_sub_release_1 < ... < CAS < Store < fetch_sub_release_n
any preceding fetch_sub_release_x
synchronizes with CAS
and CAS
is sequenced before deallocation
If the deallocation occurs in another thread, then the modification order should be
fetch_sub_release_0 < fetch_sub_release_1< ... < CAS < Store < fetch_sub_release_n < fetch_sub_release_final
Since the Arc object invoked fetch_sub_release_final
is sent by the synchronization facilities, hence CAS
still happens before fetch_sub_release_final that invokes deallocation
and any preceding fetch_sub_release_x
synchronizes with CAS
as well, so that the use(ax) still happens before the deallocation.
So, the memory order of the store
operation can be Relaxed
anyway. Is there any case I missed that requires the store to be Release
?