Domanda

Nel linguaggio di programmazione C e Pthreads come la filettatura della biblioteca;fare le variabili e le strutture che sono condivisi tra i thread devono essere dichiarati come volatile?Supponendo che potrebbero essere protette da un blocco o non (barriere, forse).

Il pthread standard POSIX hanno nulla da dire su questo, è questo compilatore-dipendente o nessuno dei due?

Edit per aggiungere:Grazie per le risposte.Ma cosa succede se siete non l'utilizzo di blocchi;che cosa succede se si sta utilizzando barriere per esempio?O il codice che utilizza le primitive come di confronto e di swap direttamente e in modo atomico modificare una variabile condivisa...

È stato utile?

Soluzione

Credo che una proprietà molto importante di volatili è che rende la variabile di essere scritto in memoria quando viene modificato, e rileggere dalla memoria ogni volta che si accede.Le altre risposte qui mix di volatili e di sincronizzazione, ed è chiaro da alcune altre risposte di questo che la volatilità NON è un sync primitiva (credito quando il credito è dovuto).

Ma a meno di non usare volatili, il compilatore è libero di cache condivisa di dati in un registro per qualsiasi lunghezza di tempo...se volete che i vostri dati per essere scritto per essere prevedibilmente scritto a memoria e non solo nella cache in un registro dal compilatore, a sua discrezione, è necessario contrassegnare come volatile.In alternativa, se è solo di accedere ai dati condivisi dopo aver lasciato una funzione di modifica, si potrebbe essere un bene.Ma vorrei suggerire di non fare affidamento sulla fortuna cieca per assicurarsi che i valori sono scritti da registri di memoria.

Soprattutto sul registro ricchi di macchine (cioè, non x86), le variabili possono vivere per periodi più o meno lunghi nei registri, e un buon compilatore è in grado di cache, anche di parti di strutture o intere strutture nei registri.Così si dovrebbe usare il volatile, ma per le prestazioni, anche la copia dei valori per le variabili locali per il calcolo e poi fare un esplicito write-back.Essenzialmente, volatile in modo efficiente significa anche fare un po ' di load / store a pensare in C codice.

In ogni caso, è positivamente necessario utilizzare un qualche tipo di a livello di sistema operativo fornito meccanismo di sincronizzazione per creare un programma corretto.

Per un esempio della debolezza di volatili, di vedere la mia Decker algoritmo esempio a http://jakob.engbloms.se/archives/65, che si rivela abbastanza bene che la volatilità non funziona la sincronizzazione.

Altri suggerimenti

Fintanto che si sta utilizzando i blocchi per il controllo dell'accesso alla variabile, non è necessario volatile su di esso.Infatti, se si sta mettendo volatile su qualsiasi variabile, probabilmente hai già sbagliato.

https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/

La risposta è assolutamente, inequivocabilmente, NO.Non è necessario utilizzare 'volatile' oltre alla corretta primitive di sincronizzazione.Tutto ciò che deve essere fatto, fatto da questi primitivi.

L'uso di 'volatile' non è né necessaria né sufficiente.Non è necessario in quanto il corretto primitive di sincronizzazione sono sufficienti.Non è sufficiente, perché si disattiva solo alcune ottimizzazioni, non tutti quelli che potrebbero mordere.Per esempio, non garantisce l'atomicità o la visibilità su un'altra CPU.

Ma a meno di non usare volatili, il compilatore è libero di cache condivisa di dati in un registro per qualsiasi lunghezza di tempo...se volete che i vostri dati per essere scritto per essere prevedibilmente scritto a memoria e non solo nella cache in un registro dal compilatore, a sua discrezione, è necessario contrassegnare come volatile.In alternativa, se è solo di accedere ai dati condivisi dopo aver lasciato una funzione di modifica, si potrebbe essere un bene.Ma vorrei suggerire di non fare affidamento sulla fortuna cieca per assicurarsi che i valori sono scritti da registri di memoria.

Giusto, ma anche se si fanno uso di volatili, la CPU è libera per memorizzare nella cache i dati condivisi in una scrittura distacco buffer per qualsiasi lunghezza di tempo.La serie di ottimizzazioni che si possono fare non è proprio la stessa come l'insieme di ottimizzazioni che 'volatile' disattiva.Quindi, se si utilizza 'volatile', è sono contando sulla fortuna cieca.

D'altra parte, se si utilizza la sincronizzazione primitive definite multi-threaded semantica, si ha la garanzia che le cose funzionano.Come un plus, non prendere l'enorme calo di prestazioni 'volatili'.E allora perché non fare le cose in quel modo?

C'è una diffusa idea che la parola chiave volatile è un bene per il multi-thread di programmazione.

Hans Boehm punti che ci sono solo tre portatile utilizza per volatili:

  • volatili può essere usato per marcare le variabili locali nel medesimo ambito di un setjmp il cui valore deve essere conservato attraverso un longjmp.Non è chiaro che cosa frazione di tali usi sarebbe rallentata, dal momento che l'atomicità e ordinare i vincoli non hanno alcun effetto se non c'è modo di condividere la variabile locale in questione.(Non è ancora chiaro quale frazione di tali usi sarebbe rallentata, richiedendo che tutte le variabili siano mantenute attraverso un longjmp, ma che è una questione separata e non è considerato qui.)
  • volatili può essere utilizzato quando le variabili non possono essere "esternamente modificato", ma la modifica, infatti, viene attivato in modo sincrono con il filo stesso, ad esempioperché il sottostante di memoria mappata in più posizioni.
  • Un volatili sigatomic_t può essere utilizzato per comunicare con un gestore di segnale nello stesso thread, in maniera limitata.Si potrebbe pensare di indebolire i requisiti per la sigatomic_t caso, ma che sembra piuttosto ovvia.

Se si sono il multi-threading per motivi di velocità, rallentando il codice non è sicuramente ciò che si desidera.Per il multi-thread di programmazione, ci sono due questioni fondamentali che la volatilità è spesso erroneamente pensato all'indirizzo:

  • atomicità
  • la coerenza di memoria, cioèla fine di un thread operazioni come si è visto da un altro thread.

Facciamo accordo con la (1) in primo luogo.Volatili non garantisce atomica legge o scrive.Per esempio, un volatile lettura o la scrittura di un 129-bit struttura non sta per essere atomica su hardware più moderno.Un volatile lettura o la scrittura di un 32-bit di tipo int è atomica su hardware più moderno, ma volatile ha nulla a che fare con esso.Si sarebbe probabilmente essere atomica senza il volatile.L'atomicità è al capriccio del compilatore.Non c'è niente in C o C++ standard che dice che deve essere atomica.

Ora considerare il problema (2).A volte i programmatori pensano di volatili come disattivare l'ottimizzazione dei volatili accessi.Che in gran parte è vero in pratica.Ma questo è solo il volatile accessi, non il non-volatile quelli.Considerare questo frammento:

 volatile int Ready;       

    int Message[100];      

    void foo( int i ) {      

        Message[i/10] = 42;      

        Ready = 1;      

    }

Sta cercando di fare qualcosa di molto ragionevole multi-threaded di programmazione:scrivere un messaggio e inviarlo a un altro thread.L'altro thread di attesa finché non si è Pronti, diventa una non-zero, e poi leggere il Messaggio.Prova a compilare con gcc -O2 -S" utilizzando gcc 4.0, o icc.Entrambi faranno il negozio Pronto, in modo che possa essere sovrapposti con il calcolo di i/10.Il riordino non è un bug del compilatore.È un aggressivo optimizer facendo il suo lavoro.

Si potrebbe pensare che la soluzione è quello di segnare tutti i tuoi i riferimenti di memoria volatile.Questo è semplicemente stupido.Come le precedenti citazioni dire, sarà solo rallentare il codice.Peggio ancora, potrebbe non risolvere il problema.Anche se il compilatore non riordinare i riferimenti, l'hardware potrebbe.In questo esempio, hardware x86 non riordinarla.Né sarà un Itanium(TM) processor, perché Itanium compilatori inserire la memory recinzioni per volatili negozi.Che un'intelligente Itanium estensione.Chip, ma come Potenza(TM) riordinare.Che cosa si ha realmente bisogno per l'ordinazione sono memoria recinzioni, chiamato anche le barriere di memoria.Una recinzione impedisce di riordino delle operazioni di memoria in tutta la recinzione, o, in alcuni casi, impedisce di riordino in una direzione.Volatile ha nulla a che fare con la memoria recinzioni.

Quindi qual è la soluzione per il multi-thread di programmazione?Utilizzare una libreria o estensione di linguaggio che implementa l'atomica e la recinzione semantica.Se usato come previsto, le operazioni in biblioteca si inserisce il diritto di recinzioni.Alcuni esempi:

  • I thread POSIX
  • Windows(TM) thread
  • OpenMP
  • TBB

Basato su articolo a cura di Arch Robison (Intel)

Nella mia esperienza, no;basta correttamente mutex te, quando si scrive a quei valori, o la struttura del vostro programma in modo tale che il thread di arresto prima che hanno bisogno di accedere ai dati che dipende da un altro thread azioni.Il mio progetto, x264, utilizza questo metodo;i thread condividono un'enorme quantità di dati, ma la maggior parte di esso non ha bisogno di mutex perché il suo sia di sola lettura o di un thread di attesa per i dati e finalizzato, prima ha bisogno di accedervi.

Ora, se si dispone di molti thread sono tutti fortemente interleaved nelle loro operazioni (da cui dipendono gli uni dagli altri' uscita su un livello di granularità molto fine), questo può essere molto più difficile, in realtà, in tal caso mi piacerebbe prendere in considerazione rivisitare il modello di threading per vedere se si può eventualmente essere fatto in modo più netto con più di separazione tra i thread.

NO.

Volatile è necessaria solo quando la lettura di una posizione di memoria che può cambiare indipendentemente dalla CPU comandi di lettura/scrittura.Nella situazione di filettatura, la CPU è in pieno controllo di scrittura/lettura per memoria per ogni thread, quindi il compilatore è in grado di assumere la memoria è coerente e ottimizza le istruzioni della CPU per ridurre inutili di accesso alla memoria.

L'uso primario per volatile per accedere memory-mapped I/O.In questo caso, il dispositivo può modificare il valore di una posizione di memoria in modo indipendente dalla CPU.Se non si utilizza volatile in questa condizione, la CPU potrebbe utilizzare un precedentemente memorizzata nella memoria il valore, invece di leggere il nuovo valore aggiornato.

Volatili sarebbe solo essere utile se avete bisogno assolutamente nessun ritardo tra il momento in cui un thread scrive qualcosa e un altro filo di legge.Senza un qualche tipo di blocco, però, non avete idea di quando l'altro thread ha scritto i dati, solo che è il più recente valore.

Per valori semplici (int e float, nelle loro diverse dimensioni) un mutex potrebbe essere eccessivo se non avete bisogno di un esplicito synch punto.Se non si utilizza un mutex o di un blocco di qualche tipo, è necessario dichiarare la variabile volatile.Se si utilizza un mutex è tutto a posto.

Per complicato tipi, è necessario utilizzare un mutex.Le operazioni su di essi non sono atomica, così si potrebbe leggere una mezza versione modificata senza un mutex.

Volatile significa che dobbiamo andare a memoria per ottenere o impostare questo valore.Se non si imposta volatile, il codice compilato può memorizzare i dati in un registro per un lungo periodo di tempo.

Che cosa questo significa è che si dovrebbe segnare variabili di condivisione tra i thread come volatile, in modo che non sono le situazioni in cui un thread inizia a modificare il valore, ma non di scrivere il risultato prima un secondo thread arriva e tenta di leggere il valore.

Volatile è un compilatore suggerimento che disabilita alcune ottimizzazioni.L'assembly di output del compilatore potrebbe essere stato sicuro senza di essa, ma si dovrebbe sempre utilizzare per i valori condivisi.

Questo è particolarmente importante se NON si utilizza il costoso sincronizzazione dei thread oggetti forniti dal sistema - si potrebbe, per esempio, abbiamo una struttura di dati in cui è possibile mantenere valida con una serie di atomic modifiche.Molti pile che non allocare memoria sono esempi di tali strutture di dati, perché è possibile aggiungere un valore alla pila quindi spostare la fine del puntatore o rimuovere un valore dallo stack dopo spostando il termine puntatore.Quando l'attuazione di una tale struttura, volatile diventa fondamentale per garantire che il vostro atomica istruzioni sono in realtà atomica.

Il motivo di fondo è che il linguaggio C semantico si basa su un single-threaded macchina astratta.E il compilatore è all'interno del proprio diritto di trasformare il programma fintanto che il programma di 'comportamenti osservabili' sulla macchina astratta rimanere invariato.E ' possibile unire adiacenti o sovrapposti di accessi alla memoria, rifare un accesso alla memoria più volte (su register spilling, per esempio), o semplicemente eliminare un accesso alla memoria, se si pensa che il programma di comportamenti, quando eseguito in un singolo thread, non cambia.Quindi, come si può sospettare, i comportamenti fare cambiare se il programma è in realtà dovrebbe essere in esecuzione in un ambiente multi-thread modo.

Come Paolo È sottolineato in un famoso Linux kernel documento:

Si _must_not_ presumere che il compilatore fare quello che vuoi con i riferimenti di memoria che non sono protetti da READ_ONCE() e WRITE_ONCE().Senza di loro, il compilatore è nel suo pieno diritto fare tutti i tipi di "creativo" trasformazioni, che sono coperti in il COMPILATORE BARRIERA sezione.

READ_ONCE() e WRITE_ONCE() sono definite come volatile getta sul fatto riferimento variabili.Quindi:

int y;
int x = READ_ONCE(y);

è equivalente a:

int y;
int x = *(volatile int *)&y;

Quindi, a meno di non fare un 'volatile' di accesso, non è certo che l'accesso avviene esattamente una volta, non importa che cosa il meccanismo di sincronizzazione che si sta utilizzando.La chiamata di una funzione esterna (pthread_mutex_lock per esempio) può forzare il compilatore a accessi alla memoria per le variabili globali.Ma questo accade solo quando il compilatore non riesce a capire se la funzione esterna di questi cambiamenti di variabili globali o non.I compilatori moderni impiegano sofisticati inter-procedura di analisi e di collegamento-l'ottimizzazione del tempo di fare questo trucco semplicemente inutile.

In sintesi, è necessario contrassegnare le variabili condivise da più thread volatili o accedervi utilizzando volatile cast.


Come Paolo McKenney ha anche sottolineato:

Ho visto la scintilla nei loro occhi quando si parla di tecniche di ottimizzazione che non volete che i vostri bambini a conoscere!


Ma vedi cosa succede C11/C++11.

Non riesco a capire.Come funziona la sincronizzazione primitive di forzare il compilatore a ricaricare il valore di una variabile?Perché non si limita ad utilizzare la copia più recente, che ha già?

Volatile significa che la variabile viene aggiornata al di fuori dell'ambito di applicazione del codice, e quindi, il compilatore non può assumere conoscere il valore corrente di esso.Anche le barriere di memoria sono inutili, come il compilatore, che è all'oscuro di barriere di memoria (giusto?), potrebbe ancora utilizzare un valore memorizzato nella cache.

Alcune persone, ovviamente per scontato che il compilatore considera la sincronizzazione chiamate come le barriere di memoria."Casey" è supponendo che non è esattamente una CPU.

Se la sincronizzazione primitive di funzioni esterne e i simboli in questione sono visibili al di fuori dell'unità di compilazione (nomi globali, esportato puntatore a funzione esportata che possono modificare il loro) quindi il compilatore li trattano -- o esterna, in funzione di chiamata-come una recinzione con rispetto per tutti esternamente visibili gli oggetti.

In caso contrario, siete sul proprio.E volatile può essere il miglior strumento a disposizione per fare il compilatore produce corrette, velocità codice.In genere non essere portatile, anche se, quando hai bisogno di volatili e di quello che effettivamente fa per voi dipende dal sistema e del compilatore.

No.

Prima, volatile non è necessaria.Ci sono molte altre operazioni che offrono garanzia di multithreading semantica che non uso volatile.Questi includono le operazioni atomiche, mutex, e così via.

Secondo, volatile non è sufficiente.Il C standard non fornisce alcuna garanzia sul comportamento multithreading per le variabili dichiarate volatile.

Quindi, non è né necessario né sufficiente, non ha molto senso nel suo utilizzo.

Unica eccezione sarebbe particolari piattaforme (ad esempio Visual Studio), dove ha documentato con multithreading semantica.

Variabili che vengono condivisi tra i thread devono essere dichiarati 'volatili'.Questo dice il compilatore che quando un thread scrive a tali variabili, la scrittura dovrebbe essere a memoria (invece di un registro).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top