32

I have some code which uses an array of int (int[]) in a thread which is activated every second.

I use lock() from std::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.

psmears
  • 26,070
  • 4
  • 40
  • 48
rainbow
  • 1,161
  • 3
  • 14
  • 29
  • 4
    what are the ways you tried and how exactly did the compiler complain? – 463035818_is_not_an_ai Sep 06 '17 at 07:36
  • Possible duplicate of [(preferably boost) lock-free array/vector/map/etc?](https://stackoverflow.com/questions/15330207/preferably-boost-lock-free-array-vector-map-etc) – Robinson Sep 06 '17 at 07:45
  • 1
    BTW, atomic doesn't guaranty lock-free, so implementation can do thing similarly to `mutex`. – Jarod42 Sep 06 '17 at 07:45
  • @tobi303 I tried to make some combinations like this: std::atomic myArray = {0,0,0}; or like this: std::atomic myArray; and so on. – rainbow Sep 06 '17 at 07:48
  • please add the code to the question and also add the compiler errors – 463035818_is_not_an_ai Sep 06 '17 at 07:49
  • 1
    There 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. – Richard Hodges Sep 06 '17 at 08:03
  • @Robinson I don't want to use boost. – rainbow Sep 06 '17 at 08:07
  • Lock-free programming is *hard*. If you're going to use something, might as well use something that's already tried and tested. – Robinson Sep 06 '17 at 08:23
  • @Robinson lock-free programming is also almost always completely un-necessary. ;-) – Richard Hodges Sep 06 '17 at 08:43
  • It'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. – Richard Hodges Sep 06 '17 at 08:48
  • @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 Sep 06 '17 at 08:49
  • ... and I believe a Microsoft "CriticalSection" is the same. – Martin Bonner supports Monica Sep 06 '17 at 08:50
  • You can relative easily create atomic chained lists, as pointer arithmetics are atomic everywhere. – peterh Sep 06 '17 at 11:21
  • why doing this? – sailfish009 Sep 06 '17 at 11:57
  • @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. – Martin Bonner supports Monica Sep 06 '17 at 16:25
  • @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. – peterh Sep 06 '17 at 16:36
  • @peterh: My point is that `void *` on its own is not atomic. You need `std::atomic` for that. – Martin Bonner supports Monica Sep 06 '17 at 16:45

2 Answers2

58

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!

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • 18
    it 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. – Richard Hodges Sep 06 '17 at 08:06
  • Short and elegant explanation. Thank you. This is answer I looked for. – rainbow Sep 06 '17 at 08:20
  • cppref states ["std::atomic may be instantiated with any TriviallyCopyable type T:"](http://en.cppreference.com/w/cpp/atomic/atomic) and afaik arrays are trivially copyable, or am I wrong? – 463035818_is_not_an_ai Sep 06 '17 at 08:26
  • 1
    @RichardHodges : Yes, I wrote this before cycling to work. As I walked out the door, I realized that problem! – Martin Bonner supports Monica Sep 06 '17 at 08:33
  • 2
    @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` when there can be no way of obtaining the value (because `operator T` would have to return `int[10]`) is pointless. – Martin Bonner supports Monica Sep 06 '17 at 08:46
  • yes, I got your point, I just am not familiar enough with the standard to prove cppref wrong – 463035818_is_not_an_ai Sep 06 '17 at 10:10
  • Transactional memory should be mentioned. – Yakk - Adam Nevraumont Sep 06 '17 at 15:02
  • @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? – Yakk - Adam Nevraumont Sep 06 '17 at 15:04
  • @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 need `auto 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! – Martin Bonner supports Monica Sep 06 '17 at 15:19
  • @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>> I guess... oh no you couldn't - atomics aren't copyable or moveable. – Richard Hodges Sep 06 '17 at 17:26
  • @RichardHodges [`This`](http://coliru.stacked-crooked.com/a/5349741604f81ec1) -- sure not the ASM instruction, but writing "compare and set" isn't impossible. – Yakk - Adam Nevraumont Sep 06 '17 at 17:46
  • @Yakk yes I see but one wonders whether just using a mutex is actually just quicker in practice. Cache lines and all that. – Richard Hodges Sep 06 '17 at 18:15
  • @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. – Yakk - Adam Nevraumont Sep 06 '17 at 18:19
  • @Yakk no, I mean being able to update elements of the array without copying the whole thing twice. – Richard Hodges Sep 06 '17 at 18:41
11

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.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 3
    bus 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 Sep 06 '17 at 08:32
  • @MartinBonner Not right now, no. I have to dig a little. I remember reading this somewhere; probably in [this book](https://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770). 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? – The Quantum Physicist Sep 06 '17 at 08:37
  • 2
    How can it be done? Easy. None of the member functions of `std::atomic` allow direct access to the underlying value, so give each `std::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 Sep 06 '17 at 08:43
  • 1
    @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. – The Quantum Physicist Sep 06 '17 at 08:51
  • 1
    `std::atomic>` 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.) – Martin Bonner supports Monica Sep 06 '17 at 08:55
  • @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). – The Quantum Physicist Sep 06 '17 at 08:57
  • Martin is correct. A c++ `std::atomic` has the same type from the perspective of all threads. They all must access the `T` values via the `std::atomic` interface. It's this interface which exhibits the observable atomic behavior, but it also is the _only_ interface to access the underlying `T`. – MSalters Sep 06 '17 at 09:56
  • 3
    I feel like "If it fails, it'll initiate a bus lock" isn't exactly true. Take a look at the signature of `atomic::load`: http://www.cplusplus.com/reference/atomic/atomic/load/ You can specify `memory_order_xxx` – KeksArmee Sep 12 '18 at 16:28