0

I see the list of builtins at https://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html. But for an atomic set, do you need to use the pair __sync_lock_test_and_set and __sync_lock_release?

I have seen this example of this on https://attractivechaos.wordpress.com/2011/10/06/multi-threaded-programming-efficiency-of-locking/.

volatile int lock = 0;
void *worker(void*)
{
    while (__sync_lock_test_and_set(&lock, 1));
    // critical section
    __sync_lock_release(&lock);
}

But if I use this example, and do my atomic set inside the critical section, then atomic sets to different variables will be unnecessarily serialized.

Appreciate any input on how to do an atomic set where I have multiple atomic variables.

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
S. Forman
  • 109
  • 2
  • 8
  • 2
    Do you really have to use those legacy builtins? https://gcc.gnu.org/onlinedocs/gcc-7.3.0/gcc/_005f_005fsync-Builtins.html#g_t_005f_005fsync-Builtins recommends to avoid them in new code. – Marc Glisse Apr 20 '18 at 16:06
  • Good idea. Most of the links I found on "atomic operations in c" were about these legacy builtins. The __atomic_store builtin could implement my atomic64_set function. – S. Forman Apr 21 '18 at 03:35

2 Answers2

0

As per definition need to use both

__sync_synchronize (...)

This builtin issues a full memory barrier. type

__sync_lock_test_and_set (type *ptr, type value, ...)

This builtin, as described by Intel, is not a traditional test-and-set operation, but rather an atomic exchange operation. It writes value into *ptr, and returns the previous contents of *ptr. Many targets have only minimal support for such locks, and do not support a full exchange operation. In this case, a target may support reduced functionality here by which the only valid value to store is the immediate constant 1. The exact value actually stored in *ptr is implementation defined.

This builtin is not a full barrier, but rather an acquire barrier. This means that references after the builtin cannot move to (or be speculated to) before the builtin, but previous memory stores may not be globally visible yet, and previous memory loads may not yet be satisfied.

void __sync_lock_release (type *ptr, ...)

This builtin releases the lock acquired by __sync_lock_test_and_set. Normally this means writing the constant 0 to *ptr. This builtin is not a full barrier, but rather a release barrier. This means that all previous memory stores are globally visible, and all previous memory loads have been satisfied, but following memory reads are not prevented from being speculated to before the barrier.

ntshetty
  • 1,293
  • 9
  • 20
  • Do you know if there is a single call to set a value? Since GCC provides operations like __sync_fetch_and_or(), it seems like there should be a __sync_set operation. If not, could you provide an example of how to. use __sync_synchronize(). Does it have a corresponding release call? Did not see one in the few examples I could find. Thanks! – S. Forman Apr 20 '18 at 04:15
0

I came up with this solution. Please reply if you know a better one:

typedef struct {
    volatile int lock;  // must be initialized to 0 before 1st call to atomic64_set
    volatile long long counter;
} atomic64_t;

static inline void atomic64_set(atomic64_t *v, long long i)
{
    // see https://attractivechaos.wordpress.com/2011/10/06/multi-threaded-programming-efficiency-of-locking/
    // for an explanation of __sync_lock_test_and_set
    while (__sync_lock_test_and_set(&v->lock, 1)) { // we don't have the lock, so busy wait until
        while (v->lock);                            // it is released (i.e. lock is set to 0)
    }                                               // by the holder via __sync_lock_release()
    // critical section
    v->counter = i;
    __sync_lock_release(&v->lock);
}
S. Forman
  • 109
  • 2
  • 8