Search code examples
cmultithreadinggccatomicmutual-exclusion

Are different atomic operations(e.g. __sync_fetch_and_add and __sync_lock_test_and_set) mutually exclusive?


I am learning atomic operations supported by hardware. I know there are many atomic operations, for example, compare and swap(CAS)、fetch and add(FAA)、test and set.

I understand that when we use just one atomic operation at a shared resource on a multi-threaded program, different threads can access the shared resource mutually exclusively. such as the following example:

    // this code implements a spinlock with CAS
    void lock_init(int *lock){
        *lock = 0;
    }

    void lock(int *lock) {
        while(__sync_val_compare_and_swap(lock, 0, 1) != 0) {
            ; // busy-looping
        }
    }

    void unlock(int *lock) {
        *lock = 0;
    }

On the code above, all threads that access the lock variable will use CAS atomic operation to operate the lock variable mutually exclusively.

However, when we use multiple different atomic operations on the same variable, can these different atomic operations access the shared variable mutually exclusively?

Take the following code as an example:

template<typename T> 
class AtomicIntegerT
{
    public:
        AtomicIntegerT()
            : value_(0) 
        {
        }
    
        T get() {
            return __sync_val_compare_and_swap(&value_, 0, 0);
        }   

        T getAndAdd(T x) {
            return __sync_fetch_and_add(&value_, x);
        }

        T addAndGet(T x) {
            return getAndAdd(x) + x;
        }

        T getAndSet(T newValue) {
            return __sync_lock_test_and_set(&value_, newValue);
        }

    private:
        volatile T value_; 
};

I want to implement atomic variables with __sync_val_compare_and_swap、__sync_fetch_and_add、__sync_lock_test_and_set. using __sync_val_compare_and_swap to get value of variable; using __sync_fetch_and_add to add some value on variable; using __sync_lock_test_and_set to implement atomic assignment operations.

when one thread is executing getAndSet()while another thread is executing getAndAdd(), can those two threads access value_ mutually exclusively?

The more general problem is that are different atomic operations(e.g. __sync_fetch_and_add and __sync_lock_test_and_set) mutually exclusive?


Solution

  • Yes, the deprecated GNU C __sync builtins and the modern GNU C __atomic builtins are all atomic operations on the object. You can use two different atomic operations from different threads and get consistent results.

    Multiple threads can __atomic_load_n in parallel, no mutual exclusion needed between pure loads. Your get would be much more efficient with that instead of __sync CAS with 0,0!

    But otherwise yes, __sync and __atomic builtins are equivalent mutual exclusion as far as operations on that object. The deprecated legacy __sync builtins are also equivalent to a full barrier as part of the operation (like atomic_thread_fence(memory_order_seq_cst), not just a seq_cst operation), so they potentially create synchronization like mutual exclusion.

    __atomic operations on different objects with orders weaker than __ATOMIC_SEQ_CST can appear to be overlapping, but each operation is still atomic.

    The __atomic builtins are what <atomic> uses to implement std::atomic<T> uses in C++. In C <stdatomic.h> and _Atomic are the portable way to use the same functionality as the __atomic builtins. (Except ISO C doesn't have a way to do non-atomic access to an object in some parts of your program, and atomic access only during multi-threaded parts. C++20 std::atomic_ref<> does that, or you can roll your own in GNU C with __atomic builtins.)