Armazenamento local de thread (TLS)

O TLS (Armazenamento Local de Threads) é o método pelo qual cada thread em um determinado processo multithread pode alocar locais nos quais armazenar dados específicos do thread. Os dados específicos de thread (em tempo de execução) associados dinamicamente têm suporte por meio da API TLS (TlsAlloc). O Win32 e o compilador do Microsoft C++ agora dão suporte a dados estaticamente associados (tempo de carga) por thread, além da implementação de API existente.

Implementação de compilador para o TLS

C++11: O especificador de classe de armazenamento thread_local é a maneira recomendada de especificar o armazenamento local de thread para objetos e membros de classe. Para obter mais informações, consulte Classes de armazenamento (C++).

O MSVC também fornece um atributo específico da Microsoft, thread, como modificador da classe de armazenamento estendida. Use a palavra-chave __declspec para declarar uma variável thread. Por exemplo, o código a seguir declara uma variável local de thread de inteiro e a inicializa com um valor:

__declspec( thread ) int tls_i = 1;

Regras e limitações

As seguintes diretrizes devem ser observadas ao declarar variáveis e objetos locais de threads associados estaticamente. Essas diretrizes se aplicam tanto ao thread quanto ao thread_local:

  • O atributo thread só pode ser aplicado a declarações e definições de classe e dados. Ele não pode ser usado em declarações ou definições de função. Por exemplo, o código a seguir gera um erro de compilador:

    __declspec( thread )void func();     // This will generate an error.
    
  • O modificador thread só pode ser especificado em itens de dados com extensão static. Isso inclui objetos de dados globais (static e extern), objetos estáticos locais e membros de dados estáticos de classes C++. Não é possível declarar objetos de dados automáticos com o atributo thread. O código a seguir gera erros do compilador:

    void func1()
    {
        __declspec( thread )int tls_i;            // This will generate an error.
    }
    
    int func2(__declspec( thread )int tls_i )     // This will generate an error.
    {
        return tls_i;
    }
    
  • As declarações e a definição de um objeto local de thread devem especificar o atributo thread. Por exemplo, o código a seguir gera um erro:

    #define Thread  __declspec( thread )
    extern int tls_i;        // This will generate an error, since the
    int __declspec( thread )tls_i;        // declaration and definition differ.
    
  • O atributo thread não pode ser usado como um modificador de tipo. Por exemplo, o código a seguir gera um erro de compilador:

    char __declspec( thread ) *ch;        // Error
    
  • Como a declaração de objetos C++ que usam o atributo thread é permitida, estes dois exemplos são semanticamente equivalentes:

    __declspec( thread ) class B
    {
    // Code
    } BObject;  // OK--BObject is declared thread local.
    
    class B
    {
    // Code
    };
    __declspec( thread ) B BObject;  // OK--BObject is declared thread local.
    
  • O endereço de um objeto local de thread não é considerado constante, e nenhuma expressão que envolva esse endereço é considerada uma expressão constante. No C padrão, o efeito é não permitir o uso do endereço de uma variável local de thread como um inicializador para um objeto ou ponteiro. Por exemplo, o código a seguir é sinalizado como um erro pelo compilador C:

    __declspec( thread ) int tls_i;
    int *p = &tls_i;       //This will generate an error in C.
    

    Essa restrição não se aplica no C++. Como o C++ permite a inicialização dinâmica de todos os objetos, você pode inicializar um objeto usando uma expressão que usa o endereço de uma variável local de thread. Isso é feito exatamente como a construção de objetos locais de thread. Por exemplo, o código mostrado anteriormente não gera um erro quando ele é compilado como um arquivo de origem C++. O endereço de uma variável local de thread é válido apenas contanto que o thread no qual o endereço foi obtido ainda exista.

  • O C padrão permite a inicialização de um objeto ou uma variável com uma expressão que envolva uma referência a si mesma, mas apenas para objetos de extensão não estática. Embora o C++ geralmente permita essa inicialização dinâmica de objetos com uma expressão que envolve uma referência a si mesma, esse tipo de inicialização não é permitido com objetos locais de thread. Por exemplo:

    __declspec( thread )int tls_i = tls_i;                // Error in C and C++
    int j = j;                               // OK in C++, error in C
    __declspec( thread )int tls_i = sizeof( tls_i )       // Legal in C and C++
    

    Uma expressão sizeof que inclui o objeto que está sendo inicializado não constitui uma referência a si mesma, e é permitida em C e C++.

    O C++ não permite essa inicialização dinâmica de dados de thread devido a possíveis aprimoramentos futuros para a instalação de armazenamento local do thread.

  • Em sistemas operacionais Windows anteriores ao Windows Vista, __declspec( thread ) tem algumas limitações. Se uma DLL declarar dados ou um objeto como __declspec( thread ), poderá causar uma falha de proteção se carregada dinamicamente. Depois que a DLL é carregada com LoadLibrary, ela causa falha no sistema sempre que o código faz referência aos dados __declspec( thread ). Como o espaço de variável global para um thread é alocado em tempo de execução, o tamanho desse espaço é baseado em um cálculo dos requisitos do aplicativo, além dos requisitos de todas as DLLs que estão vinculadas estaticamente. Quando você usa LoadLibrary, não é possível estender esse espaço para permitir as variáveis locais de thread declaradas com __declspec( thread ). Use as APIs do TLS, como TlsAlloc, em sua DLL para alocar TLS se a DLL puder ser carregada com LoadLibrary.

Confira também

Multithreading com C e Win32