Open In App

C++ 11 – <atomic> Header

Last Updated : 06 Oct, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

In C++11, the <atomic> header introduces a powerful mechanism for managing concurrent access to shared data in multithreaded applications. This header provides atomic types and operations that ensure safe access to variables, preventing data races and potential issues in multithreaded code. In this article, we’ll explore the concept of <atomic> in C++11, along with proper examples and comments to illustrate its usage.

What is <atomic> in C++11?

In multithreaded programs, multiple threads often access and modify shared data simultaneously, which can lead to data races and unpredictable behavior. <atomic> solves this problem by providing atomic operations allowing threads to access and modify variables safely, without explicit locks or synchronization mechanisms.

In atomic operation, concurrent data access is regulated by a memory model for which the behavior is well-defined if one thread tries to access the data that another thread is currently accessing.

Syntax of Atomic Variable

std::atomic <type> var_name;

where,

  • type: the type of variable that can be of any primitive data type such as int, bool, char, etc.
  • var_name: name of the atomic variable.

Atomic Operations

Atomic operations are the operations that can be done on the std::atomic type and the <atomic> header provides some functions to perform these atomic operations.

S. No.

Function

Description

1

load()

This function loads the value stored in the atomic object.

2

store()

The function stores the value in the atomic object.

3

exchange()

This function replaces the value stored in the atomic object and returns the previously stored value.

4

wait()

This function is used to block the thread.

5

notify_one()

Notifies one of the threads that was waiting for the atomic object.

6

notify_all()

Notifies all the threads that were waiting for the atomic object.

7

fetch_add()

Gets the current value stored and adds the given value to the atomic object’s value.

8

fetch_sub()

Gets the current value stored and subtracts the given value from the atomic object’s value.

9

fetch_and()

Gets the current value stored and performs bitwise AND operation with atomic value and given value.

10

fetch_or()

Gets the current value stored and performs bitwise OR operation with atomic value and given value.

11

fetch_xor()

Gets the current value stored and performs bitwise XOR operation with atomic value and given value.

Examples of std::atomic

Let’s explore some examples to understand how <atomic> works in practice.

Example 1

std::atomic can be used with various data types for atomic operations. Let’s use it with integers:

C++




// C++ Program to illustrate the usage of <atomic> Header
#include <atomic>
#include <iostream>
#include <thread>
using namespace std;
  
atomic<int> counter(0); // Atomic integer
  
void increment_counter(int id)
{
    for (int i = 0; i < 100000; ++i) {
        // Increment counter atomically
        counter.fetch_add(1);
    }
}
  
int main()
{
    thread t1(increment_counter, 1);
    thread t2(increment_counter, 2);
  
    t1.join();
    t2.join();
  
    cout << "Counter: " << counter.load() << std::endl;
  
    return 0;
}


Output

Counter: 200000

Atomic Flag

A std::atomic_flag is a simple atomic boolean type that can be used as a lock-free flag. It is a specialization of atomic<bool> type that is guaranteed to be always lock-free.

It provides the following methods for atomic operations:

S. No.

Method

Description

1

test()

Gets the value stored in the atomic flag.

2

test_and_set()

Gets the value and sets the value of the flag.

3

clear()

Reset the value of the atomic flag.

Example 2: Atomic Flag

C++14




// C++ Program to illustrate the usage of <atomic> Header
#include <atomic>
#include <iostream>
#include <thread>
using namespace std;
  
// Initialize atomic flag
atomic_flag flag = ATOMIC_FLAG_INIT;
  
void thread_function(int id)
{
    for (int i = 0; i < 5; ++i) {
        while (flag.test_and_set(memory_order_acquire)) {
        }
        // Acquire lock
        cout << "Thread " << id << " is running." << endl;
        flag.clear(memory_order_release); // Release lock
    }
}
  
// driver code
int main()
{
    thread t1(thread_function, 1);
    thread t2(thread_function, 2);
  
    t1.join();
    t2.join();
  
    return 0;
}


Output

Thread 1 is running.
Thread 1 is running.
Thread 1 is running.
Thread 1 is running.
Thread 1 is running.
Thread 2 is running.
Thread 2 is running.
Thread 2 is running.
Thread 2 is running.
Thread 2 is running.

Explanation:

  • We use std::atomic_flag as a lock to ensure that only one thread can enter the critical section at a time.
  • test_and_set sets the flag to true (acquire lock) atomically, and clear resets it (release lock).
  • The memory order parameters (std::memory_order_acquire and std::memory_order_release) ensure proper synchronization.

Related Articles

For a deeper understanding of <atomic>, you can refer to the following GeeksforGeeks articles:



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads