Is this a possible result when using atomic in asynchronous programming

use std::sync::atomic::{AtomicI32,Ordering};
static ATOM:AtomicI32 = AtomicI32::new(0);
async fn fun1(i:i32){
    blocking::unblock(move ||{
        std::thread::sleep(std::time::Duration::from_millis(120));
        ATOM.store(1, Ordering::Relaxed);
        println!("ok {i}");
    }).await;
}

#[tokio::main]
async fn main() {
    for i in 0..20{
        tokio::select! {
            _=tokio::time::sleep(std::time::Duration::from_millis(110))=>{
                let r = ATOM.load(Ordering::Relaxed);
                //std::thread::sleep(std::time::Duration::from_millis(20));
                println!("sleep {i}, detect:{r}");
            }
            _=fun1(i)=>{
                println!("fun1");
            }
        }
        println!("----------------------");
        ATOM.store(0, Ordering::Relaxed);
        //tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }
}

Consider the following example: Is this result possible?

ok
sleep, detect:0

That is, in the thread pool, the println!("ok") is executed, however, the branch at #1 wins the race, and sleep is printed, but the result of the load is 0? Is this a theoretically possible result

Yes, it's possible, and it doesn’t even depend on the details of atomics at all. Your program’s threads may be executed in this interleaved order (among many other possible orderings):

Thread Event
Main timer expires
Blocking timer expires
Main let r = ATOM.load() loads 0
Blocking ATOM.store(1, Ordering::Relaxed)
Blocking println!("ok")
Main println!("sleep, detect:{r}") prints previously read 0
Blocking finishes closure and wakes fun1

If you wanted to ensure that only one of the two things (ok or #1) happens, then you need to arrange so that the logic of #1 also writes a value to the atomic, and both sides use a compare_exchange() so they can notice if the other side wrote first. That way, both sides can come to agreement on “who wins the race”.

Maybe, I meant, the order could be

Blocking 	ATOM.store(1, Ordering::Relaxed)
Blocking 	println!("ok")
Main 	let r = ATOM.load() loads 0
Main 	println!("sleep, detect:{r}") prints previously read 0

The inter-thread latency such that the result of the load in this order is 0.

Given the granularity of timers and the operating system schedule and et cetera it's also possible for the Main and Blocking timers to expire in the other order.

This is also possible, I think, but it doesn't matter because your program doesn't ensure other orderings don't exist that produce 0. Appropriate use of atomics will fix both simultaneously.

Yes, and that doesn't change the results.

1 Like

Post a test result that can prove this is true.

PS: The result is difficult to reproduce.

Also mandatory link with explanation that if you are not using barriers even “impossible” results are, in fact, possible.