2012-08-16 11 views
70

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?Che cosa significa thread_local in C++ 11?

risposta

73

La durata di archiviazione locale del thread è un termine utilizzato per fare riferimento a dati apparentemente globali o statici (dal punto di vista delle funzioni che lo utilizzano) ma in realtà esiste 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 creato 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 seme era una variabile locale all'interno della funzione casuale, veniva inizializzato ogni volta che veniva chiamato, assegnando lo stesso numero ogni volta. Se fosse globale, i thread interferirebbero con le sequenze reciproche.

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

Entrambi questi esempi consentono alla variabile locale thread di esistere entro la 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 thread separati che modificano errno dopo che una delle tue chiamate fallisce, ma prima puoi controllare la variabile, e tuttavia vuoi solo una copia per thread.

This site ha una descrizione ragionevole dei diversi identificatori della durata di memorizzazione.

+5

+1 per il suggerimento 'strtok'! –

+3

L'utilizzo di thread locale non risolve i problemi con 'strtok'. 'strtok' è rotto anche in un singolo ambiente thread. –

+4

Mi dispiace, lascia che ti riformuli. Non introduce alcun nuovo problema con strtok :-) – paxdiablo

13

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

conseguenza, solo le variabili che potrebbero anche essere dichiarato static può essere dichiarato come thread_local, cioè variabili globali (più precisamente: variabili "a perimetro namespace"), i membri della classe statiche e variabili di blocco-statico (nel qual caso static è implicita).

Per fare un esempio, supponiamo di avere un pool di thread e volete sapere quanto bene il carico di lavoro è stato bilanciato:

thread_local Counter c; 

void do_work() 
{ 
    c.increment(); 
    // ... 
} 

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

Ciò stampare le statistiche di utilizzo di filo, per esempio con un'implementazione del genere:

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

Quando si dichiara una variabile thread_local quindi ogni thread ha la propria 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<<i; 
} 

void threadfunc(int id){ 
    f(id); 
    ++i; 
    g(); 
} 

int main(){ 
    i=9; 
    std::thread t1(threadfunc,1); 
    std::thread t2(threadfunc,2); 
    std::thread t3(threadfunc,3); 

    t1.join(); 
    t2.join(); 
    t3.join(); 
    std::cout<<i<<std::endl; 
} 

Questo codice di uscita "2349", "3249", "4239", "4329", "2439" o "3429", ma mai niente altro. Ogni thread ha la propria copia di i, a cui viene assegnato, incrementato e quindi stampato. Il thread che esegue main ha anche una sua 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 è a questo proposito particolare --- se si prende l'indirizzo di una variabile thread_local allora basta un normale puntatore a un oggetto normale, che è possibile passare liberamente 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<<i<<std::endl; 
} 

Poiché l'indirizzo di i viene passato alla funzione filo, la copia di i comune filo conduttore può essere assegnato a anche se è thread_local. Questo programma emetterà quindi "42". Se si esegue questa operazione, è necessario assicurarsi che non sia stato effettuato l'accesso a *p dopo l'uscita del thread a cui appartiene, altrimenti si otterrà un puntatore pendente e un comportamento non definito come qualsiasi altro caso in cui l'oggetto puntato viene distrutto.

thread_local le variabili 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 costruire 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 thread chiama f, quindi l'oggetto 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".

+0

Penso che sia importante notare che la copia locale del thread della variabile è una copia appena inizializzata della variabile. Cioè, se si aggiunge una chiamata 'g()' all'inizio di 'threadFunc', l'output sarà' 0304029' o qualche altra permutazione delle coppie '02',' 03' e '04'. Cioè, anche se 9 è assegnato a 'i' prima che i thread vengano creati, i thread ottengono una copia appena costruita di' i' dove 'i = 0'. Se 'i' è assegnato con' thread_local int i = random_integer() ', allora ogni thread ottiene un nuovo intero casuale. –