Cosa significa thread_local in C ++ 11?

Sono confuso con la descrizione di thread_local in C ++ 11. La mia comprensione è che ogni thread ha una copia univoca di variabili locali in una funzione. Le variabili globali / statiche sono accessibili da tutti i thread (possibilmente l’accesso sincronizzato tramite i blocchi). E le variabili thread_local sono visibili a tutti i thread ma possono essere modificate solo dal thread per il quale sono definite? È corretto?

Thread-local storage duration è un termine usato per riferirsi a dati apparentemente globali o statici (dal punto di vista delle funzioni che lo utilizzano) ma in realtà, c’è una copia per thread.

Aggiunge all’automatico corrente (esiste durante un blocco / funzione), statico (esiste per la durata del programma) e dinamico (esiste nell’heap tra allocazione e deallocazione).

Qualcosa che è thread-local viene introdotto alla creazione del thread e smaltito quando il thread si arresta.

Seguono alcuni esempi.

Pensa a un generatore di numeri casuali in cui il seme deve essere mantenuto su una base per thread. L’utilizzo di un seme locale del thread indica che ogni thread ottiene la propria sequenza di numeri casuali, indipendentemente da altri thread.

Se il tuo seme era una variabile locale all’interno della funzione casuale, sarebbe inizializzato ogni volta che lo chiamavi, dandoti lo stesso numero ogni volta. Se fosse globale, i thread interferirebbero con le sequenze reciproche.

Un altro esempio è qualcosa come strtok dove lo stato di tokenizzazione è memorizzato su una base specifica del thread. In questo modo, un singolo thread può essere sicuro che altri thread non rovinino i suoi sforzi di tokenizzazione, pur mantenendo lo stato su più chiamate a strtok – questo rende fondamentalmente strtok_r (la versione thread-safe) ridondante.

Entrambi questi esempi consentono alla variabile locale del thread di esistere all’interno della funzione che lo utilizza. Nel codice pre-threaded, sarebbe semplicemente una variabile di durata dell’archiviazione statica all’interno della funzione. Per i thread, viene modificato per il threading della durata di archiviazione locale.

Ancora un altro esempio potrebbe essere qualcosa come errno . Non vuoi che thread separati modifichino errno dopo che una delle tue chiamate fallisce, ma prima puoi controllare la variabile, e tuttavia vuoi solo una copia per thread.

Questo sito ha una descrizione ragionevole dei diversi specificatori della durata di archiviazione.

Quando dichiari una variabile thread_local ogni thread ha la sua copia. Quando ci si riferisce ad esso per nome, viene utilizzata la copia associata al thread corrente. per esempio

 thread_local int i=0; void f(int newval){ i=newval; } void g(){ std::cout<  

Questo codice genererà "2349", "3249", "4239", "4329", "2439" o "3429", ma mai altro. Ogni thread ha una propria copia di i , a cui viene assegnato, incrementato e quindi stampato. Il thread che esegue main ha anche una propria copia, che viene assegnata all'inizio e quindi lasciata invariata. Queste copie sono completamente indipendenti e ognuna ha un indirizzo diverso.

È solo il nome che è speciale in questo senso --- se si prende l'indirizzo di una variabile thread_local allora si ha solo un normale puntatore a un object normale, che può essere liberamente passato tra i thread. per esempio

 thread_local int i=0; void thread_func(int*p){ *p=42; } int main(){ i=9; std::thread t(thread_func,&i); t.join(); std::cout<  

Poiché l'indirizzo di i viene passato alla funzione thread, è ansible assegnare la copia di i appartiene al thread principale anche se è thread_local . Questo programma emetterà quindi "42". Se si esegue questa operazione, è necessario fare attenzione a non accedere a *p dopo che il thread a cui appartiene è uscito, altrimenti si ottiene un puntatore pendente e un comportamento indefinito proprio come qualsiasi altro caso in cui l'object puntato viene distrutto.

thread_local variabili thread_local vengono inizializzate "prima del primo utilizzo", quindi se non vengono mai toccate da un determinato thread, non vengono necessariamente inizializzate. Questo per consentire ai compilatori di evitare di build ogni variabile thread_local nel programma per un thread interamente autonomo e che non ne tocchi nessuno. per esempio

 struct my_class{ my_class(){ std::cout< <"hello"; } ~my_class(){ std::cout<<"goodbye"; } }; void f(){ thread_local my_class; } void do_nothing(){} int main(){ std::thread t1(do_nothing); t1.join(); } 

In questo programma ci sono 2 thread: il thread principale e il thread creato manualmente. Nessuno dei thread chiama f , quindi l'object thread_local non viene mai utilizzato. Pertanto non è specificato se il compilatore costruirà 0, 1 o 2 istanze di my_class e l'output potrebbe essere "", "hellohellogoodbyegoodbye" o "hellogoodbye".

L’archiviazione locale del thread è in ogni aspetto come l’archiviazione statica (= globale), solo che ogni thread ha una copia separata dell’object. La vita dell’object inizia o all’avvio del thread (per variabili globali) o alla prima inizializzazione (per le statistiche locali del blocco) e termina quando termina il thread (cioè quando viene chiamato join() ).

Di conseguenza, solo le variabili che potrebbero essere dichiarate static possono essere dichiarate come thread_local , cioè variabili globali (più precisamente: variabili “nello spazio dei nomi”), membri di classi statiche e variabili block-static (nel qual caso è implicita la static ).

Ad esempio, si supponga di disporre di un pool di thread e di voler sapere quanto è equilibrato il carico di lavoro:

 thread_local Counter c; void do_work() { c.increment(); // ... } int main() { std::thread t(do_work); // your thread-pool would go here t.join(); } 

Questo stamperebbe le statistiche sull’uso dei thread, ad esempio con un’implementazione come questa:

 struct Counter { unsigned int c = 0; void increment() { ++c; } ~Counter() { std::cout < < "Thread #" << std::this_thread::id() << " was called " << c << " times" << std::endl; } };