Here is the code snippet, taken from Mara Bos's book:
static mut DATA: String = String::new();
static LOCKED: AtomicBool = AtomicBool::new(false);
fn f() {
if LOCKED.compare_exchange(false, true, Acquire, Relaxed).is_ok() {
// Safety: We hold the exclusive lock, so nothing else is accessing DATA.
unsafe { DATA.push('!') };
LOCKED.store(false, Release);
}
}
fn main() {
thread::scope(|s| {
for _ in 0..100 {
s.spawn(f);
}
});
}
I understand that using the Acquire ordering for success case forms a happens-before relationship with other thread that is done with mutating the DATA as after mutation the LOCKED is released with storing false with the Release ordering. So after mutating the DATA a thread release the LOCKED with Release ordering, and other threads try to mutate the DATA loads the LOCKED with Acquire ordering. A happens-before relationship is formed.
However, using Acquire ordering for success case seems incorrect to me. In success case of compare_exchange method store happens to LOCKED using the Relaxed ordering. After compare_exchange is done with storing and before the pushing/mutating to DATA is finished, many other threads are trying to get the LOCKED. These threads must load the LOCKED with true so that they can't push to DATA at the same time. But compare_exchange did store true to LOCKED using the Relaxed ordering, LOCKED with this updated value(stored by Relaxed ordering) might not be readily available to other threads that try to get the LOCKED. Hence there is a chance of data race here.
I think the AcqRel ordering should be used for the success case of the compare_exchange method, in this case load happens with Acquire and storing is done with the Release ordering. And this Release ordering ensures other threads gets the most upadted value of LOCKED. In fact there is a mini-happens before relationship with a thread's compare_exchange method of storing to LOCKED with another thread that tries to get the LOCKED while the first thread is still mutating the DATA.
What do you think?