Is it possible to create an atomic vector or array in C++?
Solution 1
In practice, at the CPU level, there are instructions which can atomically update an int
, and a good compiler will use these for std::atomic<int>
. In contrast, there are are no instructions which can atomically update a vector of ints (for any architecture I am aware of), so there has got to be a mutex of some sort somewhere. You might as well let it be your mutex.
For future readers who haven't yet written code with the mutex:
You can't create a std::atomic
of int[10]
, because that leads to a function which returns an array - and you can't have those. What you can do, is have a std::atomic<std::array<int,10>>
int main()
{
std::atomic<std::array<int,10>> myArray;
}
Note that the compiler/library will create a mutex under the hood to make this atomic. Note further that this doesn't do what you want. It allows you to set the value of the whole array atomically.
It doesn't allow you to read the whole array, update one element, and write the whole array back atomically.
The reads and the writes will be individually atomic, but another thread can get in between the read and the write.
You need the mutex!
Solution 2
You can put arrays in atomics, but not directly. Like the other answer explain you can use std::array
. I answered this question and explained how to do something similar for a struct.
Having said that and explained the technical viability, I have to tell you something else:
PLEASE DON'T DO THAT
The power of atomic variables come from the fact that some processors can do their operations with one instruction. The C++ compiler will try to make your atomic operations happen in one instruction. If it fails, it'll initiate a bus lock, which is like a global lock of everything, until that array is updated. It's equivalent to a mutex that locks all your variables in your program. If you're concerned about performance, don't do that!
So for your case, a mutex is not a bad idea. At least you can control what is critical and improve performance.
Related videos on Youtube
rainbow
Updated on September 07, 2020Comments
-
rainbow over 3 years
I have some code which uses an array of int (
int[]
) in a thread which is activated every second.I use
lock()
fromstd::mutex
to lock this array in this thread.However I wonder if there is a way to create an atomic array (or vector) to avoid using a mutex? I tried a couple of ways, but the compiler always complains somehow?
I know there is a way to create an array of atomics but this is not the same.
-
463035818_is_not_a_number over 6 yearswhat are the ways you tried and how exactly did the compiler complain?
-
Robinson over 6 yearsPossible duplicate of (preferably boost) lock-free array/vector/map/etc?
-
Jarod42 over 6 yearsBTW, atomic doesn't guaranty lock-free, so implementation can do thing similarly to
mutex
. -
rainbow over 6 years@tobi303 I tried to make some combinations like this: std::atomic<int[]> myArray = {0,0,0}; or like this: std::atomic<int[10]> myArray; and so on.
-
463035818_is_not_a_number over 6 yearsplease add the code to the question and also add the compiler errors
-
Richard Hodges over 6 yearsThere are some very good CPPCON talks on lock-free programming and performance measuring. The short answer is don't bother unless the mutex version cannot meet the performance expectations of your users. If that's the case then in all likelihood it's your design that's wrong. An atomic store takes ~10 times as long as a non-atomic one. If you're doing a run of stores, you're better off holding a lock and doing them without atomics.
-
rainbow over 6 years@Robinson I don't want to use boost.
-
Robinson over 6 yearsLock-free programming is hard. If you're going to use something, might as well use something that's already tried and tested.
-
Richard Hodges over 6 years@Robinson lock-free programming is also almost always completely un-necessary. ;-)
-
Richard Hodges over 6 yearsIt's probably also worth mentioning that mutexes are not actually that slow on a good implementation running on a good OS (e.g. linux). In the uncontended case (the majority of cases if your design is any good), the acquisition and release of the mutex is an atomic operation.
-
Martin Bonner supports Monica over 6 years@RichardHodges : If the access is only once a second, it's definitely unnecessary. If there were a million accesses a second, lock free might be a good idea.
-
Martin Bonner supports Monica over 6 years... and I believe a Microsoft "CriticalSection" is the same.
-
peterh over 6 yearsYou can relative easily create atomic chained lists, as pointer arithmetics are atomic everywhere.
-
sailfish009 over 6 yearswhy doing this?
-
Martin Bonner supports Monica over 6 years@peterh : No they aren't! Or at least, you can't rely on other threads seeing the updated values in the order you updated them. What's scary about this, is that it will almost always work - right up until the time you demo it to a really, really, important customer.
-
peterh over 6 years@MartinBonner Typically, atomic data structures aren't very smart. I am thinking on a list which could be appended by
listVar->last = newItem;
. Of course it can be only an unidirectional list, where the last element is registered in the container, and all the elements have only a->prev
field. -
Martin Bonner supports Monica over 6 years@peterh: My point is that
void *
on its own is not atomic. You needstd::atomic<void *>
for that.
-
-
Richard Hodges over 6 yearsit might be worth mentioning that this array would be about as useful as a chocolate teapot. All you would be able to do is take copies of the entire array or replace the entire array from a copy.
-
rainbow over 6 yearsShort and elegant explanation. Thank you. This is answer I looked for.
-
463035818_is_not_a_number over 6 yearscppref states "std::atomic may be instantiated with any TriviallyCopyable type T:" and afaik arrays are trivially copyable, or am I wrong?
-
Martin Bonner supports Monica over 6 yearsbus lock: Do you have a cite for that? Surely creating a mutex under the hood is going to be much easier to implement (and much more performant).
-
Martin Bonner supports Monica over 6 years@RichardHodges : Yes, I wrote this before cycling to work. As I walked out the door, I realized that problem!
-
The Quantum Physicist over 6 years@MartinBonner Not right now, no. I have to dig a little. I remember reading this somewhere; probably in this book. On another note (not defending not providing a reference, but just for the sake of a fruitful discussion), how else can this be done? How can you ensure atomicity (done/not done with no intermediate state) without a global lock?
-
Martin Bonner supports Monica over 6 yearsHow can it be done? Easy. None of the member functions of
std::atomic
allow direct access to the underlying value, so give eachstd::atomic
a mutex, lock the mutex at the start of each member function, release it at the end. I have a feeling that you may be mis-remembering how some architectures implement atomic operations on eg a word. -
Martin Bonner supports Monica over 6 years@tobi303 : Hmm. According to cppreference, arrays of trivially copyable types are trivially copiable. That may mean there is an error in cppreference, or there is a defect in the standard. Requiring that the library allow you to instantiate
std::atomic<int[10]>
when there can be no way of obtaining the value (becauseoperator T
would have to returnint[10]
) is pointless. -
The Quantum Physicist over 6 years@MartinBonner You may be right. But let me point out a very subtle point here: It's not about thread-safety as this is not what the program has to guarantee, and that's the problem. It's about atomicity and its semantic meaning. The definition of an atomic variable is: It's a variable with operations that can be either done or not done. It cannot be in any state in between. It's not about locking access to the variables. But again, I might be misremembering this, however, I would never do atomics for more than 16 bytes.
-
Martin Bonner supports Monica over 6 years
std::atomic<std::array<unsigned char, 256>>
provides exactly that. Internally, there may be intermediate states - but they are unobservable by any C++ programme with defined behaviour. (And if the program has undefined behaviour, it becomes impossible to reason about.) -
The Quantum Physicist over 6 years@MartinBonner The observability is the subtle part that makes me worry about such usage of atomics. Let me think about this. I'll come back after I have resolved this (will probably be a while).
-
MSalters over 6 yearsMartin is correct. A c++
std::atomic<T>
has the same type from the perspective of all threads. They all must access theT
values via thestd::atomic
interface. It's this interface which exhibits the observable atomic behavior, but it also is the only interface to access the underlyingT
. -
463035818_is_not_a_number over 6 yearsyes, I got your point, I just am not familiar enough with the standard to prove cppref wrong
-
Yakk - Adam Nevraumont over 6 yearsTransactional memory should be mentioned.
-
Yakk - Adam Nevraumont over 6 years@RichardHodges I'm not sure why this is about as useful as a chocolate teapot. You'd also be able to compare-and-set, right?
-
Martin Bonner supports Monica over 6 years@Yakk: Yes, but it's not going to be
myArray.load()[3] = 2;
- that code will compile, but won't do what you want. You needauto vExpected = myArray.load(); decltype(vExpected) v; do { v = vExpected; v[3] = 2; } while (!myArray.compare_exchange_weak( vExpected, v );
- and there are probably bugs there! -
Richard Hodges over 6 years@Yakk CAS works on one memory word. An array is a bunch of contiguous words, none of which in themselves are atomic. you could have std::atomic<std::array<std::atomic<X>>> I guess... oh no you couldn't - atomics aren't copyable or moveable.
-
Yakk - Adam Nevraumont over 6 years@RichardHodges
This
-- sure not the ASM instruction, but writing "compare and set" isn't impossible. -
Richard Hodges over 6 years@Yakk yes I see but one wonders whether just using a mutex is actually just quicker in practice. Cache lines and all that.
-
Yakk - Adam Nevraumont over 6 years@RichardHodges Oh, that atomic is almost certainly implemented as a mutex guarded instance of array. You can check this by asking if it is lockfree.
-
Richard Hodges over 6 years@Yakk no, I mean being able to update elements of the array without copying the whole thing twice.
-
KeksArmee over 5 yearsI feel like "If it fails, it'll initiate a bus lock" isn't exactly true. Take a look at the signature of
atomic::load
: cplusplus.com/reference/atomic/atomic/load You can specifymemory_order_xxx