c++rustatomiccpu-architecturecontext-switch

How does atomic synchronization work on a single thread when it gets migrated to another core


Asking this question as a pseudo code, and also targeting both rust and c++ as memory model concepts are ditto

SomeFunc(){
    x = counter.load(Ordering::Relaxed)   //#1
    counter.store(x+1, Ordering::Relaxed) //#2
    y = counter.load(Ordering::Relaxed)   //#3
}

Question: Imagine SomeFunc is being executed by a thread and between #2 and #3 the thread gets interrupted and now #3 executes on different core, in this case does counter variable get synchronized with the last updated value (core 1) when it runs on another core2 (there is no explicit release/acquire). I suppose the entire cache line+thread local storage gets shelved and loaded when the thread briefly goes to sleep and comes back running on different core?


Solution

  • First of all, it should be noted that atomic instructions add synchronization, and do not remove it.

    Would you expect:

    unsigned func(unsigned* counter) {
        auto x = *counter;
        *counter = x + 1;
        auto y = *counter;
        return y;
    }
    

    To return anything else than the original value of *counter + 1?

    Yet, similarly, the thread could be moved between cores in-between two statements!

    The above code executes fine even when the core is moved because the OS takes care during the switch to appropriately synchronize between cores to preserve user-space program order.

    So, what happens when using atomics on a single thread?

    Well, you add a bit of processing overhead -- more synchronization -- and the OS still takes care during the switch to appropriately synchronize.

    Hence the effect is strictly the same.