Std :: atomic <int>: The difference between x.fetch_add (1) and x ++;

What's the difference between

extern std::atomic<int> x; int i = x++; 

and

 extern std::atomic<int> x; int i = x.fetch_add(1); 

It seems to me that the second version is safer, but I do not see differences in testing between the two versions.

+6
source share
2 answers

The difference, of course, is not safety = atomicity, which is guaranteed for both methods.

The most important difference that I think is that fetch_add() can take a different memory order argument, while for the increment operator it is always memory_order_seq_cst .

Another obvious difference is that fetch_add() can take not only 1 as an argument, but, on the other hand, operator++ will most likely be implemented using the lock inc statement (although theoretically nothing prevents the compiler from optimizing for fetch_add(1) )

Therefore, in answering your exact question, there is no semantically important difference between x++ and x.fetch_add(1) . doc says :

This function behaves as if atomic :: fetch_add was called with 1 and memory_order_seq_cst as arguments.

+4
source

C ++ 11

C ++ 11 N3337 project 29.6.5 / 33 "Requirements for operations with atomic types":

 CA ::operator++(int) volatile noexcept; CA ::operator++(int) noexcept; 

Returns: fetch_add (1)

29.6.5 / 2 clarifies C and A :

  • a refers to one of the atomic types.
  • a C refers to the corresponding non-atomic type

I could not find this explanation, but I believe that Returns: fetch_add(1) implies that fetch_add(1) is called for its side effect, of course.

It's also worth taking a look at the prefix version:

 CA ::operator++() volatile noexcept; CA ::operator++() noexcept; 

Effects: fetch_add (1)

Returns: fetch_add (1) + 1

which indicates that it returns a value of + 1, as an increment of the regular prefix for integers.

Gcc 4.8

libstdC ++ - v3 / include / std / atomic says atomic<int> inherits __atomic_base<int> :

 struct atomic<int> : __atomic_base<int> 

libstd ++ - v3 / include / bits / atomic_base.h implements it as:

 __int_type operator++(int) noexcept { return fetch_add(1); } __int_type operator++(int) volatile noexcept { return fetch_add(1); } __int_type operator++() noexcept { return __atomic_add_fetch(&_M_i, 1, memory_order_seq_cst); } __int_type operator++() volatile noexcept { return __atomic_add_fetch(&_M_i, 1, memory_order_seq_cst); } _GLIBCXX_ALWAYS_INLINE __int_type fetch_add(__int_type __i, memory_order __m = memory_order_seq_cst) noexcept { return __atomic_fetch_add(&_M_i, __i, __m); } _GLIBCXX_ALWAYS_INLINE __int_type fetch_add(__int_type __i, memory_order __m = memory_order_seq_cst) volatile noexcept { return __atomic_fetch_add(&_M_i, __i, __m); } 

I don’t understand why the postfix calls the fetch_add , and the prefix uses the built-in directly, but in the end they all boil down to the GCC internal functions __atomic_fetch_add and __atomic_add_fetch that do the real work.

+2
source

Source: https://habr.com/ru/post/973875/


All Articles