O que o thread_local significa em C ++ 11?

Estou confuso com a descrição de thread_local no C ++ 11. Meu entendimento é que cada thread tem uma cópia única de variables ​​locais em uma function. As variables ​​globais / estáticas podem ser acessadas por todos os encadeamentos (possivelmente access sincronizado usando bloqueios). E as variables ​​thread_local são visíveis para todos os threads, mas só podem ser modificadas pelo thread para o qual elas são definidas? Está correto?

A duração do armazenamento local de thread é um termo usado para se referir a dados que aparentemente são de duração de armazenamento global ou estática (do ponto de vista das funções que o utilizam), mas, na verdade, há uma cópia por thread.

Ele adiciona ao automático atual (existe durante um bloco / function), estático (existe para a duração do programa) e dynamic (existe no heap entre a alocação e a desalocação).

Algo que é thread-local é trazido à existência na criação de threads e descartado quando o thread é interrompido.

Alguns exemplos seguem.

Pense em um gerador de números randoms onde a semente deve ser mantida em uma base por thread. Usar uma semente local de encadeamento significa que cada encadeamento obtém sua própria seqüência numérica aleatória, independente de outros encadeamentos.

Se sua semente fosse uma variável local dentro da function aleatória, ela seria inicializada toda vez que você a chamasse, dando-lhe o mesmo número a cada vez. Se fosse um global, os tópicos interfeririam nas sequências um do outro.

Outro exemplo é algo como strtok que o estado de tokenização é armazenado em uma base específica do segmento. Dessa forma, um único thread pode ter certeza de que outros threads não estragam seus esforços de tokenização, enquanto ainda é capaz de manter estado através de múltiplas chamadas para strtok – isso basicamente torna o strtok_r (a versão thread-safe) redundante.

Ambos os exemplos permitem que a variável local do thread exista na function que a utiliza. No código pré-segmentado, seria simplesmente uma variável de duração de armazenamento estático dentro da function. Para encadeamentos, isso é modificado para encadear a duração do armazenamento local.

Ainda outro exemplo seria algo como errno . Você não quer threads separados modificando errno depois que uma de suas chamadas falhar, mas antes que você possa verificar a variável, e ainda assim você quer apenas uma cópia por thread.

Este site tem uma descrição razoável dos diferentes especificadores de duração de armazenamento.

Quando você declara uma variável thread_local , cada thread tem sua própria cópia. Quando você se refere a ele por nome, a cópia associada ao segmento atual é usada. por exemplo

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

Este código irá produzir "2349", "3249", "4239", "4329", "2439" ou "3429", mas nunca mais. Cada thread tem sua própria cópia de i , que é atribuída, incrementada e impressa. O encadeamento main encadeamento também possui sua própria cópia, que é designada no início e depois deixada inalterada. Essas cópias são totalmente independentes e cada uma tem um endereço diferente.

É apenas o nome que é especial a esse respeito --- se você pegar o endereço de uma variável thread_local , então você só tem um ponteiro normal para um object normal, que você pode passar livremente entre os threads. por exemplo

 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<  

Como o endereço de i é passado para a function de thread, a cópia de i pertence ao thread principal pode ser atribuída, mesmo que seja thread_local . Este programa irá assim produzir "42". Se você fizer isso, então você precisa tomar cuidado para que *p não seja acessado depois que o segmento ao qual ele pertence tenha saído, caso contrário você obterá um ponteiro pendente e um comportamento indefinido como qualquer outro caso em que o object apontado é destruído.

thread_local variables thread_local são inicializadas "antes do primeiro uso", portanto, se elas nunca são tocadas por um determinado thread, elas não são necessariamente inicializadas. Isso permite que os compiladores evitem a construção de cada variável thread_local no programa para um encadeamento totalmente independente e não toque em nenhum deles. por exemplo

 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(); } 

Neste programa existem 2 threads: o thread principal e o thread criado manualmente. Nenhuma chamada de thread f , portanto, o object thread_local nunca é usado. Portanto, não é especificado se o compilador construirá 0, 1 ou 2 instâncias de my_class e a saída poderá ser "", "hellohellogoodbyegoodbye" ou "hellogoodbye".

O armazenamento local de thread está em todos os aspectos, como armazenamento estático (= global), somente que cada thread possui uma cópia separada do object. O tempo de vida do object começa no início do thread (para variables ​​globais) ou na primeira boot (para estática local do bloco) e termina quando o thread termina (ou seja, quando join() é chamado).

Conseqüentemente, apenas variables ​​que também podem ser declaradas static podem ser declaradas como thread_local , isto é, variables ​​globais (mais precisamente: variables ​​”no escopo de namespace”), membros de classs estáticas e variables ​​estáticas de bloco (caso em que static está implícito).

Por exemplo, suponha que você tenha um conjunto de encadeamentos e queira saber se sua carga de trabalho está sendo balanceada:

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

Isso imprimiria as statistics de uso do encadeamento, por exemplo, com uma implementação como esta:

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