Непонимание атомных структур и указателей
Мой первый вопрос: есть ли способ доступа к членам структуры в объекте 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 даже для семантики получения/выпуска. Вы всегда будете видеть свой собственный магазин, но если другие потоки хранятся на одном и том же объекте, они могут наблюдать за вашим магазином, как это происходит после загрузки.