Search code examples
c++11thread-safetystdatomic

update integer array elements atomically C++


Given a shared array of integer counters, I am interested to know if a thread can atomically fetch and add an array element without locking the entire array?

Here's an illustration of working model that uses mutex to lock access to the entire array.

// thread-shared class members
std::mutex count_array_mutex_;
std::vector<int> counter_array_( 100ish );

// Thread critical section
int counter_index = ... // unpredictable index
int current_count;
{
  std::lock_guard<std::mutex> lock(count_array_mutex_);
  current_count = counter_array_[counter_index] ++;
}
// ... do stuff using current_count.

I'd like multiple threads to be able to fetch-add separate array elements simultaneously.

So far, in my research of std::atomic<int> I'm thrown off that constructing the atomic object also constructs the protected member. (And plenty of answers explaining why you can't make a std::vector<std::atomic<int> > )


Solution

  • One way:

    // Create.
    std::vector<std::atomic<int>> v(100);
    // Initialize.
    for(auto& e : v)
        e.store(0, std::memory_order_relaxed);
    
    // Atomically increment.
    auto unpredictable_index = std::rand() % v.size();
    int old = v[unpredictable_index].fetch_add(1, std::memory_order_relaxed);
    

    Note that std::atomic<> copy-constructor is deleted, so that the vector cannot be resized and needs to be initialized with the final count of elements.

    Since resize functionality of std::vector is lost, instead of std::vector you may as well use std::unique_ptr<std::atomic<int>[]>, e.g.:

    // Create.
    unsigned const N = 100;
    std::unique_ptr<std::atomic<int>[]> p(new std::atomic<int>[N]);
    // Initialize.
    for(unsigned i = 0; i < N; ++i)
        p[i].store(0, std::memory_order_relaxed);
    
    // Atomically increment.
    auto unpredictable_index = std::rand() % N;
    int old = p[unpredictable_index].fetch_add(1, std::memory_order_relaxed);