How do I initialize an array of std::atomics to zeros?
Asked Answered
G

4

10
std::array< std::atomic_size_t, 10 > A;
// ...
std::atomic_init(A, {0}); // error
A = {ATOMIC_VAR_INIT(0)}; // error

How would you initialize an array of std::atomic to 0s?

Even for loops updating one element of the array at every step does not work. What is the purpose of arrays of atomics if we can't initialize them?

I would also like to add that the actual size of my array is huge (not 10 like in the example), so I would need a direct-initialization.

Gleanings answered 17/10, 2013 at 18:50 Comment(0)
C
4
std::array<atomic_size_t, 10> arr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

or if you can compile for C++11

std::array<std::atomic_size_t, 10> arr{{{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0} }}; // double braces required

Example: https://www.ideone.com/Mj9kfE

Edit:

It just occurred to me that you are trying to store atomics, which are not copyable, into a collection that would require they be copyable (Note: I can't get to my copy of the standard at the moment. I know this holds true for the other collections, but I'm unsure if it holds true for std::array as well).

A similar problem was posted a while back: Thread-safe lock-free array

Consume answered 17/10, 2013 at 19:6 Comment(7)
Ok, but I put 10 as an example. Actually, I should initialise my array with a huge number of zeros.Gleanings
Then it's not possible to have a collection of atomic variables?! o.OGleanings
Sort of. You have to jump through a couple small hoops. The other SO question has an example of how to do it.Consume
The fact is that I noticed the question you linked before posting mine, but naively I thought they wanted to do something more advanced. Since I "simply" wanted to initialise a static array of atomics, I assumed there would have been an easier way. Also because somewhere on the Internet people define arrays of atomics like I did; now I cannot imagine how they use them.Gleanings
Sorry, had to downvote, answer is wrong. See my answer belowAssyrian
@Assyrian At one point in time, it did compile, but it seems it needs an extra set of curly braces now. I've updated the answer.Consume
std::vector requires objects to be copyable to call functions like .emplace_back which may need to allocate+copy. IIRC, you actually can make a std::vector< std::atomic<int> > vec(10) as long as you only use methods like operator[]. For std::array, it works to do arr[5].store(1) etc.Craps
A
6
std::array<std::atomic<std::size_t>, 100> A;
for(auto&x:A)
  std::atomic_init(&x,std::size_t(0));

does the job using

clang++ -std=c++11 -stdlib=libc++ -Weverything -Wno-c++98-compat

using clang-3.3. I also tried with gcc 4.8, but it doesn't support std::atomic_init(). However, I suppose you can replace std::atomic_init(&x,std::size_t(0)) with x=std::size_t(0).

Note that std::atomic<> is not copyable, which breaks some container methods (including construction of std::array<std::atomic<T>> from a T). Also, storing atomics in an array may cause false sharing, affecting performance.

EDIT 2019

The code in the accepted answer by Zac Howland does not compile (neither with clang nor with gcc). Here is a version that will

struct foo
{
    std::array<std::atomic_size_t,2> arr= {{{0},{0}}};
    std::atomic_size_t arr_alt[2] = {{0},{0}};
};
Assyrian answered 17/10, 2013 at 19:21 Comment(4)
Unfortunately, atomic_init has been deprecated in C++20 :( See wg21.link/p0883Bluegrass
@Bluegrass replaced with?Mohun
@Zebrafish: std::atomic_init is obsolete because it's guaranteed that the constructor does whatever is necessary to make the object usable, including default-construction in static storage (zero-initialized), or placement-new. See en.cppreference.com/w/cpp/atomic/atomic_init - it's only provided for compat with C, which lets you write something like _Atomic int foo; instead of _Atomic int foo = 0; for a local variable. And in C it's how you use malloced space to hold an atomic object: C docs: en.cppreference.com/w/c/atomic/atomic_initCraps
Note that it's UB to call std::atomic_init on an object that wasn't default-constructed, but it appears std::array does default-construct if you don't specify an initializer. It's also UB to use it on an object twice, so it's not guaranteed to work to zero an array this way a second time, after it's been in use. But for the first time, IDK why you'd ever want to loop instead of just array< atomic_size_t, 10 > arr = {}.Craps
C
4
std::array<atomic_size_t, 10> arr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

or if you can compile for C++11

std::array<std::atomic_size_t, 10> arr{{{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0} }}; // double braces required

Example: https://www.ideone.com/Mj9kfE

Edit:

It just occurred to me that you are trying to store atomics, which are not copyable, into a collection that would require they be copyable (Note: I can't get to my copy of the standard at the moment. I know this holds true for the other collections, but I'm unsure if it holds true for std::array as well).

A similar problem was posted a while back: Thread-safe lock-free array

Consume answered 17/10, 2013 at 19:6 Comment(7)
Ok, but I put 10 as an example. Actually, I should initialise my array with a huge number of zeros.Gleanings
Then it's not possible to have a collection of atomic variables?! o.OGleanings
Sort of. You have to jump through a couple small hoops. The other SO question has an example of how to do it.Consume
The fact is that I noticed the question you linked before posting mine, but naively I thought they wanted to do something more advanced. Since I "simply" wanted to initialise a static array of atomics, I assumed there would have been an easier way. Also because somewhere on the Internet people define arrays of atomics like I did; now I cannot imagine how they use them.Gleanings
Sorry, had to downvote, answer is wrong. See my answer belowAssyrian
@Assyrian At one point in time, it did compile, but it seems it needs an extra set of curly braces now. I've updated the answer.Consume
std::vector requires objects to be copyable to call functions like .emplace_back which may need to allocate+copy. IIRC, you actually can make a std::vector< std::atomic<int> > vec(10) as long as you only use methods like operator[]. For std::array, it works to do arr[5].store(1) etc.Craps
C
3

Actual initialization, when the array is being constructed

For actual initialization, not zeroing an existing array, an empty initializer list Just Works, zero-initializing an arbitrary sized array just like for plain int arr[N] = {};.
See https://en.cppreference.com/w/cpp/language/zero_initialization - omitted elements in a braced initializer list are implicitly zero.

This also works in static storage, like with static or at global scope.

#include <atomic>
#include <array>
#include <cstddef>

size_t foo(){
    std::array< std::atomic<size_t>, 10 > arr {};
    return arr[5].load();  // and use the array just to prove it works
}

// same asm as for a primitive array:
size_t bar(){
    std::atomic_size_t arr[10] {};
    return arr[5].load();
}

The initializer can be = {} or {} if you prefer, but ... arr[10] () makes it look like function-pointers.

See it on Godbolt - no warnings with -Wall -pedantic -std=c++11 or -std=c++23 from G++ 13.2, or clang++ 17 with or without -stdlib=libc++. MSVC's -Wall spews thousands of warnings from its standard library headers so it's unusable, but no errors from MSVC 19.37 or 19.14. Clang optimizes away the array init and load since it can see no other thread could have had a reference to the atomic objects; you can pass it to an opaque function to see clang's code-gen (as in the Godbolt link.)

I think this is truly standards-compliant and portable, not just happens-to-work on the big 3 mainstream compilers.


Non-initialization, zeroing after construction

For zeroing after the fact, like after some use but while we know no other threads could be reading or writing, memset may be safe in practice, especially if it's lock-free and/or static_assert(sizeof(std::atomic<T>) == sizeof(T)). That's definitely not standards-compliant, but it will work if the object-representation of std::atomic<T> matches the object-representation of T, which is how lock-free std::atomic is implemented in practice.

Another option to make an array that's efficiently zeroable during single-threaded phases of a problem is to use atomic_ref on an array of plain size_t. But that means more code at every place that needs to do atomic accesses, and makes it possible to accidentally do a non-atomic access without getting a warning. Hopefully that's something clang -O2 -fsanitize=threads can catch.

alignas( std::atomic_ref<size_t>::required_alignment ) std::array<size_t, 10> arr[10] = {};

Then std::fill or memset are both usable, as long as you don't create data-race UB by doing so while another thread is accessing the array (via atomic_ref or otherwise).

See Efficient way to reset array of structs which contain a std::atomic member? for more details, including the fact that in asm on real machines, zeroing a whole array with aligned wide stores (e.g. 16-byte or 32-byte) atomically zeros each element, so you'd really like to take advantage of that if you would be looping over an array and assigning zero to each element. Because compilers won't optimize atomics, you'll get a separate 8-byte store for each element. (Which is not a disaster, but definitely worse for smaller types like atomic<uint8_t>.) See Per-element atomicity of vector load/store and gather/scatter? - x86 manuals fail to guarantee this, although it's almost certainly true in practice on real hardware.

Craps answered 28/11, 2023 at 19:43 Comment(1)
And BTW, this works with clang3.5 and GCC4.7 on Godbolt (the oldest to support -std=c++11, so this doesn't rely on any new compiler features.Craps
F
0

You can use std::index_sequence to generalize this completely as shown below:

#include <array>
#include <atomic>
template<std::size_t N> constexpr auto make_array() 
{
    return []<std::size_t... Indices>(std::index_sequence<Indices...>)
    {
        return std::array<std::atomic_size_t, N>{(Indices-Indices)...};   
    }(std::make_index_sequence<N>());  
}
int main()
{   
  auto A = make_array<10>(); //equivalent to std::array< std::atomic_size_t, 10 > A = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
}

Working demo

Forlini answered 28/11, 2023 at 18:11 Comment(9)
Is there a way to do initialize this array with increasing indices like {0,1,2,3,4,5,6,7} in C++11? I know I can use your solution and replace Indices-Indices with Indices but it requires C++20.Zygosis
Yes there is most probably a way to do what you ask in C++11 as well. Feel free to ask a new separate question for your follow up question.Forlini
@Alan: Just for the record, GCC compiles this with -std=c++17. godbolt.org/z/7Gooaqs67 But yeah, clang warns about use of a C++20 feature, explicit template parameter list for lambdas. en.cppreference.com/w/cpp/utility/integer_sequence says std::index_sequence / std::make_index_sequence are only C++14, so you might be able to use that directly without wrapping it up in a function returning a lambda, and/or without as much auto. Especially since you want the 0..N-1 integer sequence values directly, not needing to cancel them.Craps
This seems vastly over-complicated for zero initialization. Am I missing some reason this would be preferable to my later answer, or was it just a case of not realizing that zero was special and an empty initializer-list would do the trick?Craps
@PeterCordes Looking at the bounty message saying "automated way" my mind directly went towards using index_sequence and yeah I missed that in this particular case where we basically want {0,0,0,0,0}, zero initialization as done in your answer can also be used. But since this is a generalized version that works for any type(not just atomic_size_t) I think this can be useful at other places as well. I agree that in this particular case/question just doing std::array< std::atomic<size_t>, 10 > arr {} is enough and should be preferred.Forlini
@user12002570: ... arr {} works for any primitive type (including FP and pointers), also for atomic aggregates (godbolt.org/z/s7dTYcW73). atomic_size_t isn't particularly special for anything except std::index_sequence vs. std::integer_sequence<T>. Where this answer is useful is when it's generalized to non-zero init, either with a linear sequence or perhaps a recurrence to left-shift of a single bit, or some math function, or whatever.Craps
@PeterCordes Yes, I am aware of that(that it works with any primitive type). When I said "it works with for any type" I meant this. Note in the above mentioned link I have an extra template type parameter T which is supplied by user that makes the solution much more generalized and useful. That is what I was referring to when I said "it works with any type and not just atomic_size_t".Forlini
Yeah, T is how yours supports types that can be initialized with a size_t(0), like a struct of integer for FP members, but not a struct with pointer members. e.g. your fails with struct a{int *p;}; on invalid conversion from unsigned long to pointer (godbolt.org/z/15j8PoWYj - your link defined a but didn't use it), since it's not a bare literal 0 which works as a null pointer constant. Zero-initialization with ... arr {} supports all of those, like I mentioned in my previous comment (aggregates). godbolt.org/z/WxfYb1713 shows a struct with a pointer member.Craps
@PeterCordes I see so the main usefulness of this generalized version is for non-zero init.Forlini

© 2022 - 2024 — McMap. All rights reserved.