Непонимание атомных структур и указателей

Мой первый вопрос: есть ли способ доступа к членам структуры в объекте atomic<struct>? Например, я получаю ошибку компилятора:

struct std::atomic<node>’ has no member named ‘data’ a.data = 0; 

в этом сегменте

struct node{
  int data;
  node* next;
};

int main(){
  atomic<node> a;
  a.data = 0;
}

Я могу обойти это, создав временный node так:

  atomic<node> a;
  node temp;
  temp.data = 0;
  a.store(temp);

но это не кажется очень элегантным.

Второй вопрос: что, если у меня есть указатель на атомный объект? Есть ли вообще доступ к элементам node напрямую? Очевидно, что следующее не компилируется, как бы это изменить, чтобы сохранить 0 в значении node в b?

atomic<node> b = new node;
b->data = 0;

Это решение, которое я нашел, но опять же, есть ли более элегантный способ сделать это?

atomic<node> *b;
node temp;
temp.data = 0;
b->store(&temp);

И, наконец, в чем разница между atomic<node*> и atomic<node>*

Ответы

Ответ 1

этот [обходной путь] не кажется очень изящным.

std::atomic<T> не может сделать произвольные операции атомарными: поддерживается только загрузка и сохранение данных. Вот почему ваш "обходной путь" на самом деле является способом обращения с атомарными объектами: вы готовите новое значение node любым способом, который вам нравится, а затем атомизировали его в переменную atomic<node>.

что, если у меня есть указатель на атомный объект? Есть ли вообще доступ к членам node напрямую?

Доступ к контенту node через указатель также не будет атомарным: поскольку std::atomic<T> может гарантировать только загрузку и сохранение его значения в качестве атомарного, он не позволяет вам обращаться к членам T, не создавая явная копия. Это хорошо, потому что это мешает читателям кода получить ложное впечатление о том, что доступ к внутренним элементам T как-то атомный.

какая разница между atomic<node*> и atomic<node>*

В первом случае атомный объект хранит указатель, к которому можно получить доступ атомарно (т.е. вы можете переназначить этот указатель на новый node атомарно). Во втором случае атомный объект хранит значение, доступ к которому можно получить атомарно, что означает, что вы можете читать и писать все node атомарно.

Ответ 2

Когда вы делаете

atomic<node> a;
node temp; // use a.load() to copy all the fields of a to temp
temp.data = 0;
a.store(temp);

вы потеряете значение поля next. Я бы предложил предложенное изменение. Если node был бы простым типом, как std:: atomic_int, я думаю, что использование оператора = было бы возможно. В противном случае нет. Я не думаю, что есть другой способ обхода вашего дела.

И, наконец, какая разница между атомными < node * > и атомных <node> *?

Если вы используете атомный < node * > операции, выполняемые в , адрес объекта node будут атомарными, а в другом случае вам необходимо выделить память для атомного объекта и операции, выполняемые в фактический объект node будет атомарным.

Ответ 3

Обратите внимание, что ваше "решение" включает неатомное чтение-изменение-запись всех членов, кроме .data.

atomic<node> a;

node temp = a.load();
temp.data = 0;
a.store(temp); // steps on any changes to other member that happened after our load

Если вам нужна структура, в которой вы можете атомизировать все элементы вместе или отдельно атомизировать один из них (без compare_exchange_weak на всей структуре), вы можете использовать объединение атомной структуры и структуры с двумя атомарными элементами. Это может быть полезно для, например, оба указателя в двойном списке или указатель + счетчик. Текущие компиляторы плохи даже при чтении одного члена атомной структуры, не делая что-то медленное, как использование CMPXCHG16B для загрузки всей структуры, а затем просто посмотрите на один элемент. (Это случай gcc6.2 даже с memory_order_relaxed).

Этот профсоюзный взлом работает только в том случае, если вы используете компилятор С++, который гарантирует, что запись одного члена объединения, а затем чтение другого в порядке, например, на C99.

Это работает для структур с максимальным размером аппаратного обеспечения cmpxchg, то есть 16B на x86-64 (если вы включите -mcx16 в gcc, используйте CMPXCHG16B, которые не поддерживали первое поколение процессоров K8, поэтому не технически базовый уровень x86-64).

Для больших структур atomic<the_whole_thing> не будет блокироваться, а чтение/запись его членам через atomic<int> в другом члене объединения не будет безопасным. Однако чтение может быть в порядке.

Это может вызвать беспорядок семантики упорядочения памяти, потому что даже сильно упорядоченный x86 может переупорядочить узкое хранилище с более широкой загрузкой, которая полностью содержит его. Если вам в основном просто нужна атомарность, это здорово, но чтение полного объекта (например, при выполнении cmpxchg) в том же потоке, который только что написал один член, требует MFENCE на x86 даже для семантики получения/выпуска. Вы всегда будете видеть свой собственный магазин, но если другие потоки хранятся на одном и том же объекте, они могут наблюдать за вашим магазином, как это происходит после загрузки.