Hi, I have few questions to verify if my understanding of memory ordering and how the relationships are established is correct.
Is it sound to use Relaxed instead of Released in those scenarios?
struct SimpleLock<T> {
locked: AtomicBool,
data: UnsafeCell<T>,
}
impl<T> SimpleLock<T> {
pub fn new(data: T) -> Self {
Self {
locked: AtomicBool::new(false),
data: UnsafeCell::new(data),
}
}
pub fn lock(&self) -> &mut T {
while self
.locked
.compare_exchange(false, true, Acquire, Relaxed)
.is_err()
{
std::hint::spin_loop();
}
unsafe { &mut *self.data.get() }
}
// At this point no reference returned from lock() must exist
pub fn unlock(&self) {
//@todo can Relaxed be used here?
self.locked.store(false, Ordering::Relaxed);
}
}
Acquire is used to make sure that unsafe { &mut *self.data.get() }
is not reordered before the call to compare_exchange(..)
. However, when unlocking I don't see any other operation in the current scope to establish a relationship with, therefore I thought that using Relaxed is enough.
If that's unsound please let me know.
static DATA: AtomicU64 = AtomicU64::new(0);
static READY: AtomicBool = AtomicBool::new(false);
fn main() {
thread::spawn(|| {
DATA.store(123, Relaxed);
READY.store(true, Release);
});
//@todo can Relaxed be used here?
while !READY.load(Relaxed) {
thread::sleep(Duration::from_millis(100));
println!("waiting...");
}
println!("{}", DATA.load(Relaxed));
}
Similar reasoning as before, READY.store(true, Release)
implies that DATA.store(123, Relaxed);
has to be always executed before, therefore READY.load(Relaxed)
in while !READY.load(Relaxed)
can only see [false, false, ..., true]
. When true
is observed then it is guaranteed that DATA.store(123, Relaxed);
must have already happened.
On the other hand, in Crust of Rust video I understand that both Acquire and Release must be used to fix the position of the unsafe get, otherwise with Relaxed it could be possible that the call to get is reordered to either before acquiring or releasing the "lock".