4

I'm confused about how does the atomic post-increment actually work. For example

std::atomic<int> a = 1; // global void func(int i) { std::cout << i; } // execute the following in two threads func(a++); 

I believe a finally becomes 3, but is it possible to see the output "11"? Is it safe to expect one of the two threads will definitely see a became 2?

0

1 Answer 1

5

First, lets look at how this work if a were not atomic:

  1. Make a copy of a
  2. Increment a
  3. Pass the copy of a to func

In the atomic case, it is similar except that the copy/increment happens atomically. The copy is still passed to func.

Since the increment/copy is atomic, the first thread to call ++ will increment it from 1 to 2, and pass 1 to func. The second one will increment it from 2 to 3, and pass 2 to func. Note, the order of the calls to func is not deterministic, but it should be called once with the value 1, and once with the value 2.

Post-increment operator++ is defined to be equivalent to .fetch_add(1).

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for the answer. Even if a finally become 3, the "11" is still considered as a data race?
@YZ.learner: With your code change to func, it should never see 11. The increments should each call fetch_add(1), which by default uses sequential consistency, which should result in it incrementing cleanly. Now, you could easily see '21' or '12'.
Being seq_cst is irrelevant here; there's only one atomic object so even a.fetch_add(1, relaxed) would still guarantee that one increment sees 1, the other sees 2. Since both 21 and 12 are possible with seq_cst (since the race to output via std::cout is separate), relaxing it to relaxed doesn't actually change anything. Relaxed atomic RMWs are still atomic, just not ordered wrt. operations on other objects.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.