2014-11-21 15 views
14

La mia prima domanda è: esiste un modo per accedere ai membri di struct in un oggetto atomic<struct>? Per esempio, ottengo l'errore del compilatore:Malinteso di strutture e puntatori atomici

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

in questo segmento

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

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

posso lavorare intorno ad esso con la creazione di un nodo temporanea in questo modo:

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

ma questo doesn sembra molto elegante

La seconda domanda è: cosa succede se ho un puntatore a un oggetto atomico? È comunque possibile accedere direttamente ai membri del nodo? Ovviamente quanto segue non si compila, come cambierei questo per memorizzare 0 nel valore del nodo in b?

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

Questa è una soluzione che ho trovato, ma ancora una volta, c'è un modo più elegante di farlo ??

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

E, infine, qual è la differenza tra atomic<node*> e atomic<node>*

+0

No, c'è solo un insieme limitato di operazioni atomiche (carico, memorizzazione, scambio, ...) –

+1

'atomico ' applica l'aggiornamento atomico del puntatore che tiene (non la cosa a cui punta il puntatore, solo il puntatore). 'atomic *' è un puntatore a un 'atomico ', il cui scopo è quello di forzare l'aggiornamento atomico dell'oggetto 'node'. – Barry

+2

Se si desidera una struttura in cui è possibile aggiornare atomicamente * entrambi i membri insieme, o modificarne separatamente uno atomicamente (senza un confronto_exchange_weak sull'intera struttura), è possibile utilizzare [un'unione di una struttura atomica e una struttura con due atomici membri] (http://stackoverflow.com/questions/38984153/implement-aba-counter-with-c11-cas/38991835#38991835). (Se stai usando un compilatore C++ che garantisce che scrivere un membro del sindacato e poi leggerne un altro è ok, come nel C99). Questo in effetti funziona (in modo efficiente) per le strutture fino alla dimensione massima che l'hardware può utilizzare in cmpxchg, ovvero 16B su x86-64. –

risposta

9

this [workaround] doesn't seem very elegant.

std::atomic<T> non può effettuare operazioni arbitrarie atomico: solo il caricamento e la memorizzazione dei dati è supportata. Questo è il motivo per cui il tuo "workaround" è in realtà il modo di trattare gli oggetti atomici: si prepara il nuovo valore node in qualsiasi modo che si desidera e quindi si imposta atomicamente in una variabile atomic<node>.

what if I have a pointer to an atomic object? Is there anyway to access the members of the node directly?

Accesso al contenuto di un nodo attraverso un puntatore non sarebbe atomico così: dal std::atomic<T> può garantire solo carico e memorizzare il valore di essere atomico, non permette di accedere T s' membri senza fare copia esplicita. Questa è una buona cosa, perché impedisce ai lettori del codice di avere una falsa impressione che l'accesso agli interni di T sia in qualche modo atomico.

what is the difference between atomic<node*> and atomic<node>*

Nel caso abeti, l'oggetto atomico memorizza un puntatore, a cui si accede atomico (cioè è possibile ri-punto questo puntatore a un nuovo nodo atomicamente). Nel secondo caso, l'oggetto atomico memorizza il valore a cui è possibile accedere in modo atomico, il che significa che è possibile leggere e scrivere l'intero node atomicamente.

+1

Quindi, se volessi fare in modo atomico un cambiamento in un membro, dovrei rendere il membro atomico e non la struct? –

+1

@MattPennington Esattamente!Se vuoi accedere ai dati all'interno di 'node' essere atomico, il tuo nodo dovrebbe assomigliare a questo:' struct node {atomic data;}; ' – dasblinkenlight

+0

Purché tu stia bene a gravare su chiunque usi la tua struct in qualsiasi circostanza con un atomico. Se sei preoccupato solo per la sicurezza dei thread in alcuni contesti specifici, potrebbe essere meglio utilizzare un mutex separato. – dlf

2

Quando si esegue

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

si perde il valore di prossimo campo. Farei il cambiamento suggerito. Se il nodo sarebbe stato un tipo semplice , come std :: atomic_int, penso che sarebbe stato possibile utilizzare l'operatore "=". Altrimenti no. Non penso che ci sia un'altra soluzione per il tuo caso.

And lastly, what is the difference between atomic < node* > and atomic < node > *?

Se si utilizza atomica < nodo *> le operazioni effettuate sul l'indirizzo di un oggetto nodo sarà mentre atomica nell'altro caso è necessario allocare memoria per l'oggetto atomica e le operazioni di fatto su l'oggetto del nodo attuale sarà atomico.

1

Si noti che la "soluzione" include una scrittura non atomica di modifica della lettura di tutti i membri diversi da .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 

Se si desidera una struttura in cui è possibile aggiornare in modo atomico tutti i membri insieme o separatamente atomicamente modificare uno di loro (senza compare_exchange_weak su tutta la struct), è possibile utilizzare a union of an atomic struct and a struct with two atomic members. Questo potrebbe essere utile per es. entrambi i puntatori in un elenco a doppio collegamento o un puntatore + contatore. Gli attuali compilatori non sono bravi nemmeno a leggere un membro di una struttura atomica senza fare qualcosa di lento come usare CMPXCHG16B per caricare l'intera struttura e quindi basta guardare un membro. (Questo è il caso su gcc6.2 anche con memory_order_relaxed).

Questa unione hack funziona solo se stai usando un compilatore C++ che garantisce che scrivere un membro del sindacato e poi leggerne un altro è ok, come nel C99.

Questo funziona per le strutture fino alla dimensione massima che l'hardware può cmpxchg, ovvero 16B su x86-64 (se si abilita -mcx16 in gcc, per utilizzare CMPXCHG16B che la prima generazione di CPU K8 non supportava, quindi non è tecnicamente baseline x86-64).

Per le strutture più grandi, atomic<the_whole_thing> non sarà bloccato, e la lettura/scrittura dei membri tramite atomic<int> in un altro membro del sindacato non sarà sicura. Leggere potrebbe ancora essere ok, comunque.

Questo può fare un casino della semantica di ordinazione della memoria, perché anche x86 fortemente ordinato può reorder a narrow store with a wider load that fully contains it. Se per lo più hai solo bisogno di atomicità, è grandioso, ma leggere l'intero oggetto (ad esempio mentre si fa un cmpxchg) nello stesso thread che ha appena scritto un membro richiede un MFENCE su x86 anche per semantica di acquisizione/rilascio. Vedrai sempre il tuo negozio, ma se altri thread stanno memorizzando nello stesso oggetto, possono osservare il tuo negozio come accade dopo il tuo caricamento.