C language – atomic operations

Basic concepts

In the C language (especially after the C11 standard), atomic operations provide a mechanism that allows programmers to directly operate on data in a concurrent environment without using mutexes or other synchronization primitives, while ensuring the integrity of the data. Completeness and consistency.

The core idea of atomic variables and atomic operations is that at any time, only one thread can see the modification operation of the variable. This modification either occurs entirely or not at all, there is no in between. Therefore, this avoids concurrency issues such as “dirty reads”.

Atomic type

C11 introduced the header file, which defines various atomic types, such as:

  • atomic_int
  • atomic_long
  • atomic_bool
    etc.

Atomic operations

When operating on variables of these atomic types, you can use the following atomic operation functions:

  • atomic_store: Atomic storage value.
  • atomic_load: Atomic read value.
  • atomic_fetch_add: Atomic addition.
  • atomic_fetch_sub: Atomic reduction.
  • atomic_exchange: Atomic exchange value.
  • atomic_compare_exchange_strong and atomic_compare_exchange_weak: Atomic comparison and exchange.

These operations ensure that reads and modifications to atomic variables are safe in a concurrent environment.

Memory order

In order to achieve efficient concurrent operations, modern hardware provides a variety of out-of-order techniques for memory access and instruction execution. This can result in different instructions and memory access sequences being observed on different cores or threads. To manage this complexity, provides different memory ordering options:

  • memory_order_relaxed: Relaxed memory order, no synchronization or ordering requirements.
  • memory_order_acquire: There will be no memory reordering before related atomic operations.
  • memory_order_release: There will be no memory reordering after the associated atomic operation.
  • memory_order_acq_rel: has both acquire and release semantics.
  • memory_order_seq_cst: Provides complete sequence consistency for all atomic operations.

Choosing an appropriate memory order is critical to ensuring the correctness and performance of concurrent programs.

In summary, atomic operations provide C programmers with a way to perform efficient and safe operations on data in a highly concurrent environment without the need for complex synchronization primitives. However, using atomic operations correctly and efficiently requires a deep understanding of the memory model, memory ordering, and the actual behavior of the hardware.

Atomic type

atomic_int

atomic_int is an atomic type provided in the C11 and C++11 standard libraries. It is an atomic version specially designed for the int type. It is used to ensure that operations on variables of type int are atomic in a multi-threaded environment, thereby avoiding data races and Other related concurrency issues.

1. Definition:

In C11, atomic_int is an atomic integer type. In C++11, it is defined as std::atomic.

2. Features:

  • Atomicity: Operations on atomic_int (such as increment, decrement, store and load) are atomic, which means that while one thread operates on it, other threads cannot Operate them simultaneously.
  • Memory order: atomic_int operations are often associated with specific memory orders, which define the visibility and ordering of the operations. For example, when a write operation performed by one thread on a certain atomic_int is visible to another thread.
  • Lock-free: Implementations of atomic_int are generally lock-free, although the specific implementation may depend on platform and hardware support. Lock-free implementations are often more efficient than using mutexes or other synchronization primitives.

3. Common operations:

Here are some atomic operations that can be performed on atomic_int:

  • load: Get the current value of the variable.
  • store: Set the value of a variable.
  • fetch_add: Increase the value of a variable and return the old value.
  • fetch_sub: Decrement the value of a variable and return the old value.
  • exchange: Sets the value of a variable and returns the old value.
  • compare_exchange_strong and compare_exchange_weak: Conditionally set the value of a variable.

4. Example:

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

atomic_int counter = 0;

void* increment(void* arg) {
   <!-- -->
    for (int i = 0; i < 1000; + + i) {
   <!-- -->
        atomic_fetch_add( & amp;counter, 1);
    }
    return NULL;
}

int main() {
   <!-- -->
    pthread_t threads[10];
    for (int i = 0; i < 10; i + + ) {
   <!-- -->
        pthread_create( & amp;threads[i], NULL, increment, NULL);
    }
    for (int i = 0; i < 10; i + + ) {
   <!-- -->
        pthread_join(threads[i], NULL);
    }
    printf("Counter: %d\\
", atomic_load( & amp;counter));
    return 0;
}

In the above example, we use atomic_int as the counter, start 10 threads, and each thread increments the counter 1000 times. Use atomic_int to ensure that all operations on counters are atomic in a concurrent environment.

In short, atomic_int is a thread-safe integer type that provides a series of atomic operations so that multiple threads can access and modify its value simultaneously and safely without worrying about data competition or other concurrency issues. .

atomic_bool

atomic_bool is an atomic type provided in the C11 and C++11 standard libraries, designed specifically for boolean types. This means that in a multi-threaded environment, operations on a variable of type atomic_bool are atomic, which is very valuable when ensuring thread safety and avoiding data races.

1. Definition:

In C11, atomic_bool is an atomic boolean type. In C++11, it is defined as std::atomic.

2. Features:

  • Atomicity: Operations on atomic_bool (such as storage, loading, logical operations) are atomic. This means that at any given moment, only one thread can operate on it, and other threads cannot see the intermediate state of the operation.

  • Memory ordering: atomic_bool provides operations associated with a specific memory ordering. These memory orders define the visibility and ordering of operations, such as when a write operation performed by one thread is visible to another thread.

  • Lock-free: On most platforms, implementations of atomic_bool are lock-free. The specific implementation may vary based on platform and hardware support, but lock-free implementations are generally more efficient than using mutexes or other synchronization primitives.

3. Common operations:
Here are some atomic operations that can be performed on atomic_bool:

  • load: Get the current value of the variable.
  • store: Set the value of a variable.
  • exchange: Sets the value of a variable and returns the old value.
  • compare_exchange_strong and compare_exchange_weak: Conditionally set the value of a variable.

4. Example:

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

atomic_bool flag = ATOMIC_VAR_INIT(false);

void* set_flag(void* arg) {
   <!-- -->
    atomic_store( & amp;flag, true);
    return NULL;
}

int main() {
   <!-- -->
    pthread_t thread;
    pthread_create( & amp;thread, NULL, set_flag, NULL);
    pthread_join(thread, NULL);
    
    bool is_set = atomic_load( & amp;flag);
    printf("Flag is %s\\
", is_set ? "set" : "not set");
    return 0;
}

In the above example, we use atomic_bool as a flag and start a thread to set the flag. Using atomic_bool ensures that all operations on flags are atomic in a concurrent environment.

In short, atomic_bool provides a thread-safe way to operate Boolean variables, which is very useful in concurrent programming, especially when it is necessary to ensure that the operation of variables is atomic.

Atomic operations

atomic_store

atomic_store is a set of atomic operation functions provided in the C11 standard, which is used to store a value into an atomic variable to ensure that this operation is atomic, that is, it will not be interrupted by other threads during the operation. This guarantee helps prevent data races and inconsistent states in concurrent environments.

1. Statement:

The prototype is:

void atomic_store(volatile A *obj, C desired);

in,

  • A must be of atomic type.
  • obj is a pointer to the atomic object to be operated on.
  • desired is the value to be stored in the atomic object.

2. Description:

atomic_store Atomically stores the value of desired into *obj