As variáveis ​​thread_local do C ++ 11 são automaticamente estáticas?

85

Existe uma diferença entre esses dois segmentos de código:

void f() {
    thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

e

void f() {
    static thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

História de fundo: originalmente eu tinha um vetor ESTÁTICO V (para manter alguns valores intermediários, ele é limpo toda vez que eu entro na função) e um programa de thread único. Quero transformar o programa em um multithreading, então de alguma forma tenho que me livrar desse modificador estático. Minha ideia é transformar cada estático em thread_local e não se preocupar com mais nada? Essa abordagem pode sair pela culatra?

Zuza
fonte
17
Ter uma thread_localvariável local não faz sentido para começar ... cada thread tem sua própria pilha de chamadas.
Konrad Rudolph,
1
Várias funções C foram originalmente escritas para retornar o endereço de variáveis ​​estáticas ou globais. Posteriormente, descobriu-se que isso causava bugs obscuros quando usado em aplicativos multi-threaded (por exemplo, errno, localtime). Além disso, às vezes é muito prejudicial proteger variáveis ​​compartilhadas com um mutex quando uma função está sendo chamada de vários threads ou ter que passar um objeto de contexto de thread entre muitos objetos e métodos de chamada. Variáveis ​​que são locais para uma solução de thread essas e outras questões.
edwinc
3
@Konrad Rudolph declarando variáveis ​​locais apenas como em staticvez de static thread_localnão inicializar uma instância da variável para cada thread.
Davide
1
@davide Esse não é o ponto, nem de mim nem do OP. Não estamos falando sobre staticvs, static thread_localmas sim sobre autovs thread_local, usando o significado pré-C ++ 11 de auto(isto é, armazenamento automático).
Konrad Rudolph
1
Veja também Como definir variáveis ​​estáticas locais de thread? . Uma rápida nota de advogado sobre idiomas ... O suporte da Microsoft e TLS mudou em torno do Vista; consulte Armazenamento local de thread (TLS) . A mudança afeta coisas como Singleton e pode ou não ser aplicável. Se você estiver usando o modelo de software abondware, provavelmente não terá problemas. Se você está feliz em oferecer suporte a vários compiladores e plataformas, deve prestar atenção a isso.
jww 01 de

Respostas:

94

De acordo com o padrão C ++

Quando thread_local é aplicado a uma variável de escopo de bloco, a estática do especificador de classe de armazenamento está implícita se não aparecer explicitamente

Então, isso significa que esta definição

void f() {
    thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

é equivalente a

void f() {
    static thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

No entanto, uma variável estática não é o mesmo que uma variável thread_local.

1 Todas as variáveis ​​declaradas com a palavra-chave thread_local têm duração de armazenamento de thread. O armazenamento dessas entidades deve durar até o encadeamento em que foram criadas. Há um objeto distinto ou referência por thread, e o uso do nome declarado se refere à entidade associada ao thread atual

Para distinguir essas variáveis, o padrão apresenta uma nova duração de armazenamento de thread de termo junto com a duração de armazenamento estático.

Vlad de Moscou
fonte
1
statice, externportanto, não implica classe de armazenamento, mas apenas ligação para variáveis ​​thread_local, mesmo em escopos internos.
Deduplicator de
4
@Deduplicator As variáveis ​​de escopo do bloco não têm ligação. Então, seu currículo está errado. Como escrevi no post, eles têm duração de armazenamento de thread. Na verdade, é o mesmo que a duração do armazenamento estático, mas aplicado a cada thread.
Vlad de Moscou de
1
Se você adicionar o externo, estará fazendo uma declaração, não uma definição. Então?
Deduplicator
1
@Deduplicator Portanto, isso significa que as definições das variáveis ​​de escopo do bloco não têm ligação.
Vlad de Moscou de
1
Eu tentei isso no VS 2013 e ele grita "Variáveis ​​TL não podem ser inicializadas dinamicamente". Estou confuso.
v.oddou
19

Sim, "armazenamento local de thread" é ​​muito semelhante a "global" (ou "armazenamento estático"), mas ao invés de "duração de todo o programa" você tem "duração de todo o thread". Portanto, uma variável local do segmento de bloco local é inicializada na primeira vez que o controle passa por sua declaração, mas separadamente em cada segmento, e é destruída quando o segmento termina.

Kerrek SB
fonte
6

Quando usado com thread_local, staticestá implícito no escopo do bloco (veja a resposta de @Vlad), exigido para um membro da classe; Eu acho que significa ligação para o escopo do namespace.

Por 9.2 / 6:

Dentro de uma definição de classe, um membro não deve ser declarado com o especificador de classe de armazenamento thread_local, a menos que também seja declarado como estático

Para responder à pergunta original:

As variáveis ​​thread_local do C ++ 11 são automaticamente estáticas?

Não há escolha, exceto para variáveis ​​de escopo de namespace.

Existe uma diferença entre esses dois segmentos de código:

Não.

vehsakul
fonte
4

O armazenamento local do thread é estático, mas se comporta de maneira bem diferente do armazenamento estático simples.

Quando você declara uma variável estática, há exatamente uma instância da variável. O sistema de compilador / tempo de execução garante que ele será inicializado para você algum tempo antes de realmente usá-lo, sem especificar exatamente quando (alguns detalhes omitidos aqui).

O C ++ 11 garante que essa inicialização será thread-safe; no entanto, antes do C ++ 11, essa segurança de thread não era garantida. Por exemplo

static X * pointer = new X;

poderia vazar instâncias de X se mais de um thread atingir o código de inicialização estática ao mesmo tempo.

Quando você declara um segmento de variável local, existem potencialmente muitas instâncias da variável. Você pode pensar neles como se estivessem em um mapa indexado por thread-id. Isso significa que cada thread vê sua própria cópia da variável.

Mais uma vez, se a variável for inicializada, o sistema compilador / runtime garante que essa inicialização acontecerá antes que os dados sejam usados ​​e que a inicialização acontecerá para cada thread que usar a variável. O compilador também garante que a iniciação será thread-safe.

As garantias de segurança de thread significam que pode haver um pouco de código nos bastidores para fazer a variável se comportar da maneira que você espera - especialmente considerando que o compilador não tem como saber com antecedência exatamente quantos threads existem em seu programa e quantos deles tocarão na variável local do thread.

Dale Wilson
fonte
@Etherealone: ​​Interessante. Quais informações específicas? Você pode fornecer uma referência?
Dale Wilson,
1
stackoverflow.com/a/8102145/1576556 . O artigo da Wikipedia C ++ 11 menciona se bem me lembro.
Etherealone
1
No entanto, os objetos estáticos são inicializados primeiro e, em seguida, atribuída a cópia. Portanto, também não estou certo se a inicialização thread-safe inclui a expressão completa. Provavelmente sim, porque do contrário não seria considerado thread-safe.
Etherealone