0
use std::sync::atomic::AtomicPtr;
fn get_data() -> &'static Data {
    static PTR: AtomicPtr<Data> = AtomicPtr::new(std::ptr::null_mut());

    let mut p = PTR.load(Acquire);

    if p.is_null() {
        p = Box::into_raw(Box::new(generate_data()));
        if let Err(e) = PTR.compare_exchange(
            std::ptr::null_mut(), p, Release, Acquire
        ) {
            // Safety: p comes from Box::into_raw right above,
            // and wasn't shared with any other thread.
            drop(unsafe { Box::from_raw(p) });
            p = e;
        }
    }

    // Safety: p is not null and points to a properly initialized value.
    unsafe { &*p }
}

If I change the first PTR.load to Relaxed, what will be the effect?

It comes from Rust Atomics and Locks, but it doesn't make explanation about the first PTR.load. I don't quite understand why I can't use Relaxed here.

I think the release exchange guaranteed that the object will be fully created, so the load operation won't get older or incomplete data. And when PTR.load(Acquire) it will also happen that multiple threads may detect a null pointer in the PTR at the same time, and more than one thread may try to create and initialize the Data object. So I don't know the effect here.

  • 1
    https://preshing.com/20120913/acquire-and-release-semantics/ explains release and acquire in general. `acquire` has some ordering wrt. other operations in the same thread, and can sync-with a release store to create a happens-before relationship. `relaxed` can't do any of that; it's not safe to dereference a pointer you loaded with `relaxed` if you the writing thread also wrote the pointed-to data. – Peter Cordes Mar 12 '23 at 04:42
  • Data-dependency ordering (like C++ `memory_order_consume`) will often make it safe anyway to deref a pointer you loaded with `relaxed`, on architectures other than DEC Alpha which can appear to violate causality and get stale data *after* loading a new pointer. Compiler optimizations can break stuff that depends on `relaxed` working like `consume`, e.g. if the compiler proves that the pointer can only have one value in some branch, and uses that without a data dependency. – Peter Cordes Mar 12 '23 at 04:48
  • See also [C++11: the difference between memory\_order\_relaxed and memory\_order\_consume](https://stackoverflow.com/a/59832012) / [Memory order consume usage in C11](https://stackoverflow.com/a/55741922) – Peter Cordes Mar 12 '23 at 04:48
  • 1
    Normally the load to get an initial `expected` value for a CAS can be `relaxed`, but here you only attempt the CAS if the value is `null`. Otherwise the code will deref the loaded pointer, if I'm reading it right, potentially a pointer that was recently stored by another thread as `release`. – Peter Cordes Mar 12 '23 at 04:54

0 Answers0