std:: atomic: разница между x.fetch add (1) и x++;

в чем разница между

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

и

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

Я чувствую, что вторая версия безопаснее, но я не видел никаких различий в тестировании между этими двумя версиями.

2 ответов


разница определенно не в безопасности = атомарности, которая гарантирована для обоих методов.

самое важное различие, я думаю, что fetch_add() может принимать другой аргумент порядка памяти, в то время как для оператора инкремента он всегда memory_order_seq_cst.

еще одно очевидное различие заключается в том, что fetch_add() можно взять не только 1 в качестве аргумента, а с другой стороны, operator++, скорее всего, будет реализован с помощью lock inc инструкция (хотя, теоретически ничто не мешает компилятору такой оптимизации для fetch_add(1) а)

таким образом, отвечая на ваш точный вопрос, нет никакой семантически важной разницы между x++ и x.fetch_add(1). The док говорит:

эта функция ведет себя так, как если бы atomic::fetch_add был вызван с 1 и memory_order_seq_cst в качестве аргументов.


C++11

на в C++11 N3337 проект 29.6.5 / 33 "требования к операциям с атомными типами":

C A ::operator++(int) volatile noexcept;
C A ::operator++(int) noexcept;

возвращает: fetch_add(1)

29.6.5 / 2 уточняет C и A:

  • A относится к одному из атомарных типов.
  • в C относится к соответствующему неатомарный типа

Я не смог найти это объясняло ясно, но я полагаю Returns: fetch_add(1) означает, что fetch_add(1) вызвано для своего побочного эффекта конечно.

также стоит посмотреть на версию префикса немного дальше:

C A ::operator++() volatile noexcept;
C A ::operator++() noexcept;

эффекты: fetch_add(1)

возвращает: fetch_add(1) + 1

что означает, что этот возвращает значение + 1, как обычный префикс инкремента для целых чисел.

GCC 4.8

libstdc++ - v3 / include/std / atomic говорит atomic<int> наследует __atomic_base<int>:

struct atomic<int> : __atomic_base<int>

libstdc++ - v3 / include/bits / atomic_base.h реализует его как:

__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); }

Я не понимаю, почему постфикс называет fetch_add helper и префикс используют встроенный непосредственно, но в конце концов все они сводятся к GCC intrinsics __atomic_fetch_add и __atomic_add_fetch которые делают реальную работу.