How to use atomic variables in C?

30,911

Solution 1

If you are using GCC on your CentOS platform, then you can use the __atomic built-in functions.

Of particular interest might be this function:

— Built-in Function: bool __atomic_always_lock_free (size_t size, void *ptr)
This built-in function returns true if objects of size bytes always generate lock free atomic instructions for the target architecture. size must resolve to a compile-time constant and the result also resolves to a compile-time constant.

ptr is an optional pointer to the object that may be used to determine alignment. A value of 0 indicates typical alignment should be used. The compiler may also ignore this parameter.

      if (_atomic_always_lock_free (sizeof (long long), 0))

Solution 2

C11 atomic primitives

http://en.cppreference.com/w/c/language/atomic

_Atomic const int * p1;  // p is a pointer to an atomic const int
const atomic_int * p2;   // same
const _Atomic(int) * p3; // same

Added in glibc 2.28. Tested in Ubuntu 18.04 (glibc 2.27) by compiling glibc from source: Multiple glibc libraries on a single host Later also tested on Ubuntu 20.04, glibc 2.31.

Example adapted from: https://en.cppreference.com/w/c/language/atomic

main.c

#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>

atomic_int acnt;
int cnt;

int f(void* thr_data)
{
    (void)thr_data;
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
        // for this example, relaxed memory order is sufficient, e.g.
        // atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
    }
    return 0;
}

int main(void)
{
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);

    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

Compile and run:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out

Possible output:

The atomic counter is 10000
The non-atomic counter is 8644

The non-atomic counter is very likely to be smaller than the atomic one due to racy access across threads to the non atomic variable.

Disassembly analysis at: How do I start threads in plain C?

Solution 3

I am going to toss in my two cents in case someone benefits. Atomic operations are a major problem in Linux. I used gatomic.h at one time only to find it gone. I see all kinds of different atomic options of either questionable reliability or availability -- and I see things changing all the time. They can be complex with tests needed by O/S level, processor, whatever. You can use a mutex -- not only complex by dreadfully slow.

Although perhaps not ideal in threads, this works great for atomic operations on shared memory variables. It is simple and it works on every O/S and processor and configuration known to man (or woman), dead reliable, easy to code, and will always work.

Any code can me made atomic with a simple primitive -- a semaphore. It is something that is true/false, 1/0, yes/no, locked/unlocked -- binary.

Once you establish the semaphore:

set semaphore   //must be atomic

do all the code you like which will be atomic as the semaphore will block for you

release semaphore  //must be atomic

Relatively straight forward except the "must be atomic" lines.

It turns out that you easily assign your semaphores a number (I use a define so they have a name like "#define OPEN_SEM 1" and "#define "CLASS_SEM 2" and so forth.

Find out your largest number and when your program initializes open a file in some directory (I use one just for this purpose). If not there create it:

if (ablockfd < 0) {         //ablockfd is static in case you want to 
                            //call it over and over           
    char *get_sy_path();                
    char lockname[100];                 

    strcpy(lockname, get_sy_path());    
    strcat(lockname, "/metlock");       
    ablockfd = open(lockname, O_RDWR);
    //error code if ablockfd bad
}

Now to gain a semaphore:

Now use your semaphore number to "lock" a "record" in your file of length one byte. Note -- the file will never actually occupy disk space and no disk operation occurs.

//sem_id is passed in and is set from OPEN_SEM or CLASS_SEM or whatever you call your semaphores.

lseek(ablockfd, sem_id, SEEK_SET); //seeks to the bytes in file of 
                                   //your semaphore number
result = lockf(ablockfd, F_LOCK, 1);   

if (result != -1) {                    
   //got the semaphore
} else {
    //failed
}

To test if the semaphore is held:

result = lockf(ablockfd, F_TEST, 1);  //after same lseek

To release the semaphore:

result = lockf(ablockfd, F_ULOCK, 1);   //after same lseek

And all the other things you can do with lockf -- blocking/non-blocking, etc.

Note -- this is WAY faster than a mutex, it goes away if the process dies (a good thing), simple to code, and I know of no operating system with any processor with any number of them or number of cores that cannot atomically lock a record ... so simple code that just works. The file never really exists (no bytes but in directory), seems to be no practical limit to how many you may have. I have used this for years on machines with no easy atomic solutions.

Share:
30,911
lppier
Author by

lppier

Senior software engineer with machine learning and data science expertise.

Updated on September 02, 2020

Comments

  • lppier
    lppier almost 4 years

    I need to use an atomic variable in C as this variable is accessed across different threads. Don't want a race condition.

    My code is running on CentOS. What are my options?