Question on Atomic operation

I used loom to check this, and it passed. However, I don't know why it works :laughing:

use loom::sync::atomic::{AtomicBool, Ordering};
use loom::sync::{Arc, Condvar, Mutex};
use loom::thread;

fn main() {
    loom::model(|| {
        let x = Arc::new(AtomicBool::new(false));
        let x1 = x.clone();
        let x2 = x.clone();
        let lock = Arc::new(Mutex::new(false));
        let lock1 = lock.clone();
        // cvar is used just to ensure thread1 do things first. 
        let cvar = Arc::new(Condvar::new());
        let cvar1 = cvar.clone();

        let jh1 = thread::spawn(move || {
            let mut guard = lock.lock().unwrap();
            x1.store(true, Ordering::Relaxed);    // ???
            *guard = true;
            cvar.notify_one();
            // drop guard, the thread2 continues to work
        });
        let jh2 = thread::spawn(move || {
            // if thread2 got the lock first, then give it out to thread1
            let mut guard = lock1.lock().unwrap();
            while !*guard {
                guard = cvar1.wait(guard).unwrap();
            }
            let x = x2.load(Ordering::Relaxed);    // ???
            assert!(x);
        });
        jh1.join().unwrap();
        jh2.join().unwrap();
    });
}

What confused me a lot is:

  • thread1 stores x in Ordering::Relaxed
  • thread2 loads x in Ordering::Relaxed
  • Although Mutex is used to ensure thread2 continues after thread1, no Ordering::Acquire nor Ordering::Release exists, why the storing (thread1) is guaranteed to be seen by the loading(thread2)?

Edit: fix some grammer mistakes

x is the same atomic object in both threads. Thus, Ordering::Relaxed is enough to ensure that both threads agree on what the value is.

You only need Acquire and Release if you need to consider not just the atomic itself, but also non-atomic accesses. All orderings are equivalent if you only consider the effects on a single atomic; orderings other than Relaxed are about the effects on other objects in memory.

1 Like

yes but, in this example, the variable x is actually synchronized with lock too.

2 Likes

See this classic example (we only use Ordering::Relaxed):

initial state: x = 0, y = 1

THREAD 1        THREAD 2
y = 3;          if x == 1 {
x = 1;              y *= 2;
                }

thread2 sees x==1, but it also sees y==1, so the output is y==2.

If it is "same obj" that leads thread2 correctly get value in thread1, how to explain thread2 sees y=1?

Ordering-wise, acquiring a mutex guard is an acquire operation, and releasing a mutex guard is a release operation. In fact, this is where the words acquire/release come from. Thread 2 sees the store from thread one because the mutex introduces sufficient synchronization.

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.