1

I know std::atomics are supposed to have well defined behavior, but I can't find an easy-to-digest online answer to this question: Do std::atomic.load() and .store() have execution guarantees?

If two threads attempt a concurrent write or read on the same std::atomic object, are both the write and read guaranteed to be executed? In other words, is it possible either the write or read task will simply not get done? Will one or both be blocked? Or are they guaranteed to be sequentialized? I am NOT asking here about order of operations. I am asking simply if the operation will be done at some unspecified time in the future.

RoyGNEU
  • 63
  • 5
  • 2
    Can you paste you code that you mentioned in the context? – Albin Paul Mar 26 '22 at 16:10
  • 3
    "*I know atomics are supposed to have well defined behavior in a data race*" No, a data race is always UB. Certain uses of atomics however do not *cause* a data race that would have for non-atomic objects. – Nicol Bolas Mar 26 '22 at 16:10
  • Probably running into aba problem but all anyone can do is guess without any code. Don’t worry, we won’t steal your secrets – Taekahn Mar 26 '22 at 16:34
  • There is no "well defined order" between independent operations in separate threads. Both operations will take place, but unless there is some other mechanism for guaranteeing their order, either one can come before the other. – Pete Becker Mar 26 '22 at 16:36
  • @PeteBecker, it seems that you are saying that YES there is a guarantee of execution. Is this correct? I'm sorry I didn't word the question clearly enough, but I could care less about order or timing of execution (which is what all the help/docs I can find are focused on) - only that it is done. I'm specifically asking about std::atomic.load() and store() and have edited the OP to reflect that. – RoyGNEU Mar 26 '22 at 16:57
  • You can't do atomic operations concurrently by definition. – molbdnilo Mar 26 '22 at 17:03
  • @NicolBolas, I edited my wording and title to make my question more clear and also to correct the semantic problem that led you to misunderstand it and help others if they are asking the same question. – RoyGNEU Mar 26 '22 at 17:05
  • 1
    @molbdnilo, so, by the definition, when two atomic operations are *attempted* at the same time, is one dropped? both dropped? or does one wait/block while the other executes? Again, the specific implementation I'm concerned about is std::atomic – RoyGNEU Mar 26 '22 at 17:07
  • @AlbinPaul, I removed the reference to my context because 1) my question is about general knowledge and is not about fixing any specific bug and 2) even if I shared my specific bug I don't think I have the time (or skill) to create a minimum working example that would be short enough to be feasible. – RoyGNEU Mar 26 '22 at 17:17
  • 4
    When two atomic operations are started at the same time, the processor sequentialize them. It is typically done by locking the cache-line holding the atomic value (at least on x86-64 processors). – Jérôme Richard Mar 26 '22 at 17:17
  • 2
    "If I do the std::atomic write and read concurrently, are they both guaranteed to be executed (of course, in the specified order)?" - Both `.store()` and `.load()` are guaranteed to be eventually executed. But I unsure what do you mean by "specified order". If you **enforce** some order between those operations, then they are no longer **concurrent**, don't you think? You question is actually not clear. – Tsyvarev Mar 26 '22 at 17:20

1 Answers1

5

It is a basic assumption that the compiler and processor ensures that the programmed operations are executed. This has nothing to do with std::atomic<>. The guarantee which std::atomic<> offers is that single operations happen atomically.

So what does that mean?

Consider two threads, A and B, which both increment the same integer variable. This operation typically involves reading the integer, adding 1 and writing the result (notice that this is only an example, the C++ standard does not say anything about how operations are broken into atomic steps and thus we cannot make any assumptions about it based on the standard).

In this case we would have the steps "read", "add one" and "write". For each thread, these steps are guaranteed to the executed in that order, but there is no guarantee how the steps are interleaved. It may be:

   B: read
   B: add1
   B: write
   A: read
   A: add1
   A: write

which results in the integer being incremented twice.

It could also be

   A: read
   A: add1
   B: read
   B: add1
   B: write
   A: write

which would result in the integer being incremented only once.

Thus this implementation would have a race condition.

To get rid of that we can use a std::atomic<int> instead of a plain int for the integer. std::atomic<int> implements the ++ operator and the guarantee which std::atomic<> provides is that incrementing in this case will happen atomically.

In the example, this means that the sequence of steps - read, add one, write - will not be interrupted by another thread. There is still no guarantee about the order of execution between the threads. Hence, we could have

   A: read
   A: add1
   A: write
   B: read
   B: add1
   B: write

or

   B: read
   B: add1
   B: write
   A: read
   A: add1
   A: write

but other combinations will not be possible and in both cases the integer will be incremented twice. Thus there is no race condition.

nielsen
  • 5,641
  • 10
  • 27