Armazenamento local de thread - Thread-local storage

Armazenamento local de thread ( TLS ) é um método de programação de computador que usa memória estática ou global local para um thread .

Embora o uso de variáveis ​​globais seja geralmente desencorajado na programação moderna, os sistemas operacionais legados como o UNIX são projetados para hardware de uniprocessador e requerem algum mecanismo adicional para reter a semântica de APIs pré- reentrantes . Um exemplo de tais situações é quando as funções usam uma variável global para definir uma condição de erro (por exemplo, a variável global errnousada por muitas funções da biblioteca C). Se errnofosse uma variável global, uma chamada de uma função do sistema em um thread pode sobrescrever o valor previamente definido por uma chamada de uma função do sistema em um thread diferente, possivelmente antes que o código seguinte nesse thread diferente possa verificar a condição de erro. A solução é ter errnouma variável que pareça global, mas de fato exista uma vez por thread - ou seja, ela reside no armazenamento local de thread. Um segundo caso de uso seria vários threads acumulando informações em uma variável global. Para evitar uma condição de corrida , todo acesso a essa variável global teria que ser protegido por um mutex . Alternativamente, cada thread pode se acumular em uma variável local do thread (que, por definição, não pode ser lida ou gravada em outros threads, implicando que não pode haver nenhuma condição de corrida). Os encadeamentos então precisam apenas sincronizar um acúmulo final de sua própria variável local do encadeamento em uma única variável verdadeiramente global.

Muitos sistemas impõem restrições ao tamanho do bloco de memória local do thread, na verdade, limites bastante rígidos. Por outro lado, se um sistema pode fornecer pelo menos um endereço de memória (ponteiro) de tamanho variável thread-local, então isso permite o uso de blocos de memória de tamanho arbitrário em uma maneira local de thread, alocando tal bloco de memória dinamicamente e armazenando o endereço de memória desse bloco na variável local do segmento. Em máquinas RISC , a convenção de chamada geralmente reserva um registro de ponteiro de thread para esse uso.

Implementação do Windows

A função da interface de programação de aplicativos (API) TlsAllocpode ser usada para obter um índice de slot TLS não usado ; o índice de slot TLS será então considerado 'usado'.

As funções TlsGetValuee TlsSetValuesão então usadas para ler e gravar um endereço de memória em uma variável de segmento local identificada pelo índice de slot TLS . TlsSetValueafeta apenas a variável para o segmento atual. A TlsFreefunção pode ser chamada para liberar o índice de slot TLS .

Há um Win32 Thread Information Block para cada thread. Uma das entradas neste bloco é a tabela de armazenamento local do segmento para esse segmento. TlsAlloc retorna um índice para esta tabela, exclusivo por espaço de endereço, para cada chamada. Cada encadeamento possui sua própria cópia da tabela de armazenamento local do encadeamento. Portanto, cada encadeamento pode usar independentemente TlsSetValue (índice) e obter o valor especificado por meio de TlsGetValue (índice), porque eles configuram e procuram uma entrada na própria tabela do encadeamento.

Além da família de funções TlsXxx, os executáveis ​​do Windows podem definir uma seção que é mapeada para uma página diferente para cada thread do processo em execução. Ao contrário dos valores TlsXxx, essas páginas podem conter endereços arbitrários e válidos. Esses endereços, no entanto, são diferentes para cada thread em execução e, portanto, não devem ser passados ​​para funções assíncronas (que podem ser executadas em uma thread diferente) ou de outra forma passados ​​para o código que assume que um endereço virtual é único em todo o processo. As seções TLS são gerenciadas usando paginação de memória e seu tamanho é quantizado para um tamanho de página (4kB em máquinas x86). Essas seções só podem ser definidas dentro de um executável principal de um programa - DLLs não devem conter essas seções, porque elas não são inicializadas corretamente ao carregar com LoadLibrary.

Implementação de Pthreads

Na API Pthreads , a memória local para um thread é designada com o termo dados específicos do thread.

As funções pthread_key_createe pthread_key_deletesão usadas respectivamente para criar e excluir uma chave para dados específicos de thread. O tipo da chave é explicitamente deixado opaco e é referido como pthread_key_t. Esta chave pode ser vista por todos os tópicos. Em cada thread, a chave pode ser associada a dados específicos de thread via pthread_setspecific. Os dados podem ser recuperados posteriormente usando pthread_getspecific.

Além disso, pthread_key_createpode aceitar opcionalmente uma função destruidora que será chamada automaticamente na saída do thread, se os dados específicos do thread não forem NULL . O destruidor recebe o valor associado à chave como parâmetro para que possa realizar ações de limpeza (fechar conexões, liberar memória, etc.). Mesmo quando um destruidor é especificado, o programa ainda deve chamar pthread_key_deletepara liberar os dados específicos do segmento no nível do processo (o destruidor libera apenas os dados locais para o segmento).

Implementação específica da linguagem

Além de depender dos programadores para chamar as funções de API apropriadas, também é possível estender a linguagem de programação para suportar o armazenamento local de thread (TLS).

C e C ++

Em C11 , a palavra _Thread_local- chave é usada para definir variáveis ​​locais de thread. O cabeçalho <threads.h>, se compatível, é definido thread_localcomo um sinônimo para essa palavra-chave. Exemplo de uso:

#include <threads.h>
thread_local int foo = 0;

C ++ 11 introduz a thread_localpalavra - chave que pode ser usada nos seguintes casos

  • Variáveis ​​de nível de namespace (globais)
  • Variáveis ​​estáticas de arquivo
  • Variáveis ​​estáticas de função
  • Variáveis ​​de membro estático

Além disso, várias implementações de compilador fornecem maneiras específicas de declarar variáveis ​​locais de thread:

Em versões do Windows anteriores ao Vista e Server 2008, __declspec(thread)funciona em DLLs apenas quando essas DLLs estão vinculadas ao executável e não funcionará para aqueles carregados com LoadLibrary () (pode ocorrer uma falha de proteção ou corrupção de dados).

Lisp comum (e talvez outros dialetos)

O Common Lisp fornece um recurso chamado variáveis ​​de escopo dinâmico .

Variáveis ​​dinâmicas têm uma ligação que é privada para a invocação de uma função e todos os filhos chamados por essa função.

Essa abstração mapeia naturalmente para armazenamento específico de thread, e as implementações Lisp que fornecem threads fazem isso. Common Lisp tem várias variáveis ​​dinâmicas padrão e, portanto, os threads não podem ser adicionados de maneira sensata a uma implementação da linguagem sem que essas variáveis ​​tenham semântica local de thread na vinculação dinâmica.

Por exemplo, a variável padrão *print-base*determina a raiz padrão na qual os inteiros são impressos. Se esta variável for substituída, então todo o código envolvente imprimirá inteiros em uma raiz alternativa:

;;; function foo and its children will print
;; in hexadecimal:
(let ((*print-base* 16)) (foo))

Se as funções podem ser executadas simultaneamente em diferentes threads, essa ligação deve ser adequadamente no thread local, caso contrário, cada thread lutará por quem controla uma raiz de impressão global.

D

No D versão 2, todas as variáveis ​​estáticas e globais são thread-local por padrão e são declaradas com sintaxe semelhante às variáveis ​​globais e estáticas "normais" em outras linguagens. Variáveis ​​globais devem ser solicitadas explicitamente usando a palavra-chave compartilhada :

int threadLocal;  // This is a thread-local variable.
shared int global;  // This is a global variable shared with all threads.

A palavra-chave compartilhada funciona como classe de armazenamento e como qualificador de tipo - as variáveis compartilhadas estão sujeitas a algumas restrições que impõem estaticamente a integridade dos dados. Para declarar uma variável global "clássica" sem essas restrições, a palavra-chave não segura __gshared deve ser usada:

__gshared int global;  // This is a plain old global variable.

Java

Em Java , variáveis ​​locais de thread são implementadas pelo objeto de ThreadLocal classe . ThreadLocal contém a variável do tipo T, que é acessível por meio de métodos get / set. Por exemplo, a variável ThreadLocal que contém o valor inteiro tem a seguinte aparência:

private static final ThreadLocal<Integer> myThreadLocalInteger = new ThreadLocal<Integer>();

Pelo menos para Oracle / OpenJDK, isso não usa armazenamento local de thread nativo, apesar de threads de sistema operacional serem usados ​​para outros aspectos de threading Java. Em vez disso, cada objeto Thread armazena um mapa (não seguro para thread) de objetos ThreadLocal para seus valores (em oposição a cada ThreadLocal tendo um mapa de objetos Thread para valores e incorrendo em uma sobrecarga de desempenho).

Linguagens .NET: C # e outras

Em linguagens .NET Framework , como C # , os campos estáticos podem ser marcados com o atributo ThreadStatic :

class FooBar
{
    [ThreadStatic]
    private static int _foo;
}

No .NET 4.0, a classe System.Threading.ThreadLocal <T> está disponível para alocar e carregar lentamente variáveis ​​locais de segmento.

class FooBar
{
    private static System.Threading.ThreadLocal<int> _foo;
}

Além disso, uma API está disponível para alocar dinamicamente variáveis ​​locais de thread.

Object Pascal

Em Object Pascal (Delphi) ou Free Pascal, a palavra-chave reservada threadvar pode ser usada em vez de 'var' para declarar variáveis ​​usando o armazenamento local de thread.

var
   mydata_process: integer;
threadvar
   mydata_threadlocal: integer;

Objective-C

No Cocoa , GNUstep e OpenStep , cada objeto NSThread tem um dicionário thread-local que pode ser acessado por meio do método threadDictionary do thread .

NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary];
dict[@"A key"] = @"Some data";

Perl

Em Perl, os tópicos foram adicionados no final da evolução da linguagem, depois que um grande corpo de código existente já estava presente na Comprehensive Perl Archive Network (CPAN). Portanto, os threads em Perl, por padrão, usam seu próprio armazenamento local para todas as variáveis, para minimizar o impacto dos threads no código existente sem reconhecimento de thread. Em Perl, uma variável compartilhada por thread pode ser criada usando um atributo:

use threads;
use threads::shared;

my $localvar;
my $sharedvar :shared;

PureBasic

No PureBasic, as variáveis ​​de thread são declaradas com a palavra-chave Threaded .

Threaded Var

Pitão

No Python versão 2.4 ou posterior, a classe local no módulo de threading pode ser usada para criar armazenamento local de thread.

import threading
mydata = threading.local()
mydata.x = 1

Rubi

Ruby pode criar / acessar variáveis ​​locais de thread usando os métodos [] = / []:

Thread.current[:user_id] = 1

Ferrugem

Variáveis ​​locais de thread podem ser criadas no Rust usando a thread_local!macro fornecida pela biblioteca padrão do Rust:

use std::cell::RefCell;
use std::thread;

thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

FOO.with(|f| {
    assert_eq!(*f.borrow(), 1);
    *f.borrow_mut() = 2;
});

// each thread starts out with the initial value of 1, even though this thread already changed its copy of the thread local value to 2
let t = thread::spawn(move|| {
    FOO.with(|f| {
        assert_eq!(*f.borrow(), 1);
        *f.borrow_mut() = 3;
    });
});

// wait for the thread to complete and bail out on panic
t.join().unwrap();

// original thread retains the original value of 2 despite the child thread changing the value to 3 for that thread
FOO.with(|f| {
    assert_eq!(*f.borrow(), 2);
});

Referências

links externos