ID do artigo: 163449 - Última revisão: quarta-feira, 21 de fevereiro de 2007 - Revisão: 4.2

Uso do armazenamento local de thread em um procedimento armazenado estendido

Dica do SistemaEste artigo aplica-se a um sistema operativo diferente do que está a utilizar. Foi desactivado o conteúdo do artigo, que pode não ser relevante para si.

Nesta página

Expandir tudo | Recolher tudo

Sumário

Armazenamento de thread local (TLS) pode ser um assunto muito complicado. Ao trabalhar em um segmento de pool de ambiente, é muito importante que você entenda as implicações de TLS e as interações de thread.

É altamente recomendável que você evitar TLS em um segmento de pool de ambiente, como Microsoft SQL Server. No entanto, se você deve usar o TLS, verifique cuidadosamente ler este documento e consulte a documentação on-line no SDK do Win32 para as principais funções TLS e DLL.

Além disso, o __declspec(thread) de diretiva de compilador não tem suporte em um procedimento armazenado estendido. Quando LoadLibarary ocorre, a definição de __declspec(thread) não foi inicializada corretamente. Consulte "Windows Advanced" por Jeffrey Ricter para obter mais detalhes.



Observação: Qualquer extensão para SQL Server, incluindo procedimentos armazenados estendidos, nunca pode fazer uma chamada para DisableThreadLibraryCalls().

Mais Informações

Conforme documentado, a função TlsAlloc retorna um valor de índice que é usado em chamadas de função para TlsSetValue e TlsGetValue. A documentação sugere que você chama a função TlsAlloc em sua função DLLMain quando ul_reason_for_call = DLL_PROCESS_ATTACH. Por padrão, DLL_PROCESS_ATTACH é chamado quando a DLL é carregada inicialmente.

Em DLL_THREAD_ATTACH e DLL_PROCESS_ATTACH, você deve alocar memória e chamar TlsSetValue. Isso é especificamente onde o pool de segmentos começa a causar alguns problemas no cenário.

Abaixo está um cenário de exemplo que se aplica a qualquer aplicativo que thread do pool. Este exemplo usa o aplicativo do Microsoft SQL Server.

A seguir estão algumas noções básicas para o exemplo que você deve compreender. Esses todos os são documentados em detalhes mais na documentação do SDK do Win32 em função DLLMain.
  • Quando uma DLL é carregada, ele chama DLL_PROCESS_ATTACH.
  • Todos os threads subseqüentes (gerados depois que a DLL é carregada) chamam DLL_THREAD_ATTACH.
  • Segmentos em execução no momento (gerados antes que a DLL é carregada) não chamam DLL_THREAD_ATTACH.
  • Todos os threads chamam DLL_THREAD_DETACH, mesmo se eles nunca chamado DLL_THREAD_ATTACH.
Por exemplo, suponha que o gráfico a seguir mostra a ordem e o uso do pool de segmento de trabalho do SQL Server:
   Thread     ATTACH CALLED     COMMAND     USER
------------------------------------------------ 
     1              NO           select      Joe
     2             YES          xp_test     Mary
     3             YES           select     Adam

     1              NO          xp_test     Lynn
				

Jorge inicia uma instrução select execução demorada. Ninguém tem usado xp_test procedimento armazenado estendido ainda, portanto, não há nenhuma maneira para o processo DLL_ATTACH ser chamado.

Enquanto estiver executando o selecione de José, Mary executará xp_test. O mecanismo do pool de segmento determina que um novo segmento deve ser gerado a solicitação de serviço Mary. SQL Server, em seguida, chama a função LoadLibrary para carregar o arquivo Xproc.dll. Ao fazer isso, o thread 2 é o primeiro thread para anexar o para a DLL, de modo que é chamado de DLL_PROCESS_ATTACH. Conforme discutido anteriormente, TlsAlloc pode ser chamado para inicializar valor de índice TLS do procedimento armazenado estendido.

Enquanto os comandos de João e de Maria estiverem sendo executados, o ADAM envia seu próprio selecione. Novamente, um novo segmento é gerado para manipular a solicitação de ADAM. Porque o thread 3 é gerado após a LoadLibrary ocorreu, thread 3 chama DLL_THREAD_ATTACH. Conforme documentado, isso é onde você poderia alocar a memória para thread 3 e chamar TlsSetValue.

Agora, suponha que selecione de José estiver concluída, portanto, segmento de trabalho 1 está livre para uso. Lynn envia o comando xp_test e é atribuída o thread 1. O thread 1 nunca chama DLL_PROCESS_ATTACH ou DLL_THREAD_ATTACH, porque o thread foi gerado antes da LoadLibrary foi chamada.

Neste exemplo, você pode ver que qualquer tentativa de thread 1 para acessar a memória TLS com TlsGetValue resultará em um ponteiro NULL que está sendo retornado. Se o procedimento armazenado estendido não está escrito corretamente para verificar essa condição, você encontrará uma violação de acesso (VA) ao tentar gravar o endereço NULL.

Abaixo estão vários pontos que precisa ser mencionado sobre TLS e pool de segmentos.

Se você estiver usando TLS em um ambiente de pool, você sempre deve verificar TlsGetValue para um valor de retorno NULL. Quando chegar um NULL, você deve alocar corretamente memória e chamada TlsSetValue para manipular os segmentos que onde gerado antes da LoadLibrary ocorreu.

Outra limitação abordada não diretamente a documentação é que você irá alocar a memória TLS na função DLLMain para cada thread gerado após LoadLibarary, mesmo se o thread nunca usa as funções na DLL. O exemplo acima mostra isso com thread 3. Executar somente uma seleção, mas ele chamado DLL_THREAD_ATTACH, que alocada a memória TLS para o procedimento armazenado estendido.

Se você colocar esse design em um segmento de pool de ambiente, você pode alocar memória que nunca pode ser usada.

Este é a melhor maneira para otimizar a alocação de memória:
  1. Em DLL_PROCESS_ATTACH, obtenha o valor de índice TLS correto TlsAlloc.
  2. Não alocar memória no DLL_PROCESS_ATTACH ou DLL_THREAD_ATTACH.
  3. Criar uma função genérica para obter o valor TLS; se ele for NULL, alocá-lo corretamente e chamar TlsSetValue.

    Esta etapa restringe a alocação de memória para os threads que realmente usá-lo. Isso pode reduzir significativamente o tempo de sobrecarga e inicialização de segmentos de trabalho. Ele também cria a redundância para tratar corretamente esses segmentos que foram gerados antes da LoadLibrary ocorreu.
  4. Porque DLL_PROCESS_DETACH e DLL_THREAD_DETACH sempre são chamados, Libere memória desses processos.
A somente outra coisa que ainda pode estar incorreta é a chamada para TlsAlloc. Ler a documentação, ele poderá parecer que o valor de índice TLS retornado é um valor global. Ele é um valor global, mas cada chamada para TlaAlloc retorna um valor de índice diferentes. Isso permite que dois procedimentos armazenados estendidos ter seu próprio valor de índice TLS e manipular corretamente seus próprios dados TLS sem afetar os outros.

Este é um procedimento armazenado estendido que mostra o comportamento descrito no exemplo:
   // 
   //    1. Start SQL Server from the command prompt to see the output:
   //       ...\Mssql\Binn\Sqlservr -c
   // 
   //    2. Run Xproctst.cmd to show the behavior.
   // 
   #include "windows.h"
   #include "stdio.h"
   #include "srv.h"
 
   #define           TLS_FAIL    0xFFFFFFFF

   DWORD          dwTlsIndex     =  TLS_FAIL;
   DWORD          dwCounter      =  0;
   CRITICAL_SECTION  csSync;

   // 
   //    Cleanup TLS memory
   // 
   void vCleanUpTls(void)
   {
      char *   strData     =  NULL;
      if(TLS_FAIL != dwTlsIndex)
      {
         strData = TlsGetValue(dwTlsIndex);
         if(NULL != strData)
         {
            free(strData);
            printf("\n >>> Tls memory released by thread %ld",
   GetCurrentThreadId());
         }
      }
   }
   // 
   //    Setup the TLS pointer
   // 
   void vSetUpTls(void)
   {
      char *      strData  =  NULL;
      if(TLS_FAIL == dwTlsIndex)
      {
         dwTlsIndex = TlsAlloc();
      }
      // 
      //    Are we ready to go
      // 
      if(TLS_FAIL != dwTlsIndex)
      {
         strData = (char *) calloc(256,1);
         if(strData)
         {
            printf("\n >>> Tls memory allocated by thread %ld",
   GetCurrentThreadId());
            if(TRUE == TlsSetValue(dwTlsIndex, strData))
            {
               // 
               //    Protect the counter.
               // 
               EnterCriticalSection(&csSync);
               sprintf(strData, "Counter = %ld", ++dwCounter);
               LeaveCriticalSection(&csSync);
            }
            else
            {
               printf("\n >>> *** Serious error *** TlsSetValue
               failed.\n");
            }
         }
         else
         {
            printf("\n >>> *** Serious error *** can not allocate
            memory.\n");
         }
      }
      else
      {
         printf("\n >>> *** Serious error *** TlsAlloc failed.\n");
      }
   }
   // 
   //    DLLMain
   // 
   BOOL APIENTRY DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID
   lpReserved)
   {
      char  strInfo[256]   =  "";
      switch(ul_reason_for_call)
      {
         case DLL_PROCESS_ATTACH:
            InitializeCriticalSection(&csSync);
            vSetUpTls();
            sprintf(strInfo, "\n >>> DLL_PROCESS_ATTACH Thread: %ld",
   GetCurrentThreadId());
            break;
        case DLL_THREAD_ATTACH:
            vSetUpTls();
            sprintf(strInfo, "\n >>> DLL_THREAD_ATTACH Thread: %ld",
   GetCurrentThreadId());
            break;
         case DLL_PROCESS_DETACH:
            vCleanUpTls();
            if(TLS_FAIL != dwTlsIndex)
               TlsFree(dwTlsIndex);
            DeleteCriticalSection(&csSync);
            sprintf(strInfo, "\n >>> DLL_PROCESS_DETACH Thread: %ld",
   GetCurrentThreadId());
            break;
         case DLL_THREAD_DETACH:
            vCleanUpTls();
            sprintf(strInfo, "\n >>> DLL_THREAD_DETACH Thread: %ld",
   GetCurrentThreadId());
            break;
      }
      printf(strInfo);
      return TRUE;
   }
   // 
   //    DLL function in the extended stored procedure to show TLS trap
   // 
   __declspec(dllexport) SRVRETCODE xp_Tls(SRV_PROC *pSrvProc)
   {
      char  strInfo[256]   =  "";
      char *   strData        =  NULL;
      sprintf(strInfo, "\n >>> Invoking xp_Tls on Thread: %ld",
   GetCurrentThreadId());
      printf(strInfo);
      if(TLS_FAIL != dwTlsIndex)
      {
         strData = TlsGetValue(dwTlsIndex);
         if(NULL != strData)
         {
            sprintf(strInfo, "\n >>> %s", strData);
            printf(strInfo);
         }
         else
         {
            printf("\n >>> *** Serious error *** TlsGetValue returned NULL,
   thread pooling not handled correctly.\n");
         }
      }
      return 1;
   }
				

Mais Informações

SQL Server 7.0 e SQL Server 2000 fibras

É altamente desencorajar usando TLS e nós não oferecem suporte usando TLS no modo fibra. No modo de fibra, o thread físico pode ser alterado por vários motivos, tornando qualquer TLS não seguros.

A informação contida neste artigo aplica-se a:
  • Microsoft SQL Server 2000 Enterprise Edition
  • Microsoft SQL Server 7.0 Standard Edition
  • Microsoft SQL Server 6.0 Standard Edition
  • Microsoft SQL Server 6.5 Standard Edition
Palavras-chave: 
kbmt kbhowto kbother kbprogramming kbusage KB163449 KbMtpt
Tradução automáticaTradução automática
IMPORTANTE: Este artigo foi traduzido por um sistema de tradução automática (também designado por Machine Translation ou MT), não tendo sido portanto traduzido ou revisto por pessoas. A Microsoft possui artigos traduzidos por aplicações (MT) e artigos traduzidos por tradutores profissionais, com o objetivo de oferecer em português a totalidade dos artigos existentes na base de dados de suporte. No entanto, a tradução automática não é sempre perfeita, podendo conter erros de vocabulário, sintaxe ou gramática. A Microsoft não é responsável por incoerências, erros ou prejuízos ocorridos em decorrência da utilização dos artigos MT por parte dos nossos clientes. A Microsoft realiza atualizações freqüentes ao software de tradução automática (MT). Obrigado.
Clique aqui para ver a versão em Inglês deste artigo: 163449  (http://support.microsoft.com/kb/163449/en-us/ )