Pools de threads

Um pool de threads é uma coleção de threads de trabalho que executam com eficiência retornos de chamada assíncronos em nome do aplicativo. O pool de threads é usado principalmente para reduzir o número de threads de aplicativo e fornecer gerenciamento dos threads de trabalho. Os aplicativos podem enfileirar itens de trabalho, associar o trabalho a identificadores de espera, fazer fila automaticamente com base em um temporizador e associar-se a E/S.

Arquitetura do pool de threads

Os aplicativos a seguir podem se beneficiar do uso de um pool de threads:

  • Um aplicativo altamente paralelo e que pode expedir um grande número de pequenos itens de trabalho de forma assíncrona (como pesquisa de índice distribuído ou E/S de rede).
  • Um aplicativo que cria e destrói um grande número de threads que são executados por um curto período de tempo. O uso do pool de threads pode reduzir a complexidade do gerenciamento de threads e a sobrecarga envolvida na criação e destruição de threads.
  • Um aplicativo que processa itens de trabalho independentes em segundo plano e em paralelo (como carregar várias guias).
  • Um aplicativo que deve executar uma espera exclusiva em objetos kernel ou bloquear eventos de entrada em um objeto . O uso do pool de threads pode reduzir a complexidade do gerenciamento de threads e aumentar o desempenho reduzindo o número de opções de contexto.
  • Um aplicativo que cria threads de garçom personalizados para aguardar eventos.

O pool de threads original foi completamente rearquitecado no Windows Vista. O novo pool de threads é aprimorado porque fornece um único tipo de thread de trabalho (dá suporte a E/S e não E/S), não usa um thread de temporizador, fornece uma única fila de temporizador e fornece um thread persistente dedicado. Ele também fornece limpo grupos, maior desempenho, vários pools por processo agendados de forma independente e uma nova API de pool de threads.

A arquitetura do pool de threads consiste no seguinte:

  • Threads de trabalho que executam as funções de retorno de chamada
  • Threads de garçom que esperam em várias alças de espera
  • Uma fila de trabalho
  • Um pool de threads padrão para cada processo
  • Uma fábrica de trabalho que gerencia os threads de trabalho

Práticas Recomendadas

A nova API do pool de threads fornece mais flexibilidade e controle do que a API do pool de threads original. No entanto, há algumas diferenças sutis, mas importantes. Na API original, a redefinição de espera era automática; na nova API, a espera deve ser redefinida explicitamente a cada vez. A API original lidou com a representação automaticamente, transferindo o contexto de segurança do processo de chamada para o thread. Com a nova API, o aplicativo deve definir explicitamente o contexto de segurança.

Veja a seguir as práticas recomendadas ao usar um pool de threads:

  • Os threads de um processo compartilham o pool de threads. Um único thread de trabalho pode executar várias funções de retorno de chamada, uma de cada vez. Esses threads de trabalho são gerenciados pelo pool de threads. Portanto, não encerre um thread do pool de threads chamando TerminateThread no thread ou chamando ExitThread de uma função de retorno de chamada.

  • Uma solicitação de E/S pode ser executada em qualquer thread no pool de threads. O cancelamento de E/S em um thread de pool de threads requer sincronização porque a função cancel pode ser executada em um thread diferente daquele que está tratando a solicitação de E/S, o que pode resultar no cancelamento de uma operação desconhecida. Para evitar isso, forneça sempre a estrutura OVERLAPPED com a qual uma solicitação de E/S foi iniciada ao chamar CancelIoEx para E/S assíncrona ou use sua própria sincronização para garantir que nenhuma outra E/S possa ser iniciada no thread de destino antes de chamar a função CancelSynchronousIo ou CancelIoEx .

  • Limpe todos os recursos criados na função de retorno de chamada antes de retornar da função. Elas incluem TLS, contextos de segurança, prioridade de thread e registro COM. As funções de retorno de chamada também devem restaurar o estado do thread antes de retornar.

  • Mantenha os identificadores de espera e seus objetos associados ativos até que o pool de threads tenha sinalizado que ele foi concluído com o identificador.

  • Marque todos os threads que estão aguardando operações longas (como liberações de E/S ou limpeza de recursos) para que o pool de threads possa alocar novos threads em vez de esperar por este.

  • Antes de descarregar uma DLL que usa o pool de threads, cancele todos os itens de trabalho, E/S, operações de espera e temporizadores e aguarde a conclusão da execução de retornos de chamada.

  • Evite deadlocks eliminando dependências entre itens de trabalho e entre retornos de chamada, garantindo que um retorno de chamada não esteja aguardando a conclusão e preservando a prioridade do thread.

  • Não enfileira muitos itens muito rapidamente em um processo com outros componentes usando o pool de threads padrão. Há um pool de threads padrão por processo, incluindo Svchost.exe. Por padrão, cada pool de threads tem no máximo 500 threads de trabalho. O pool de threads tenta criar mais threads de trabalho quando o número de threads de trabalho no estado pronto/em execução deve ser menor que o número de processadores.

  • Evite o modelo de apartment com thread único COM, pois ele é incompatível com o pool de threads. O STA cria um estado de thread que pode afetar o próximo item de trabalho para o thread. O STA geralmente é de longa duração e tem afinidade de thread, que é o oposto do pool de threads.

  • Crie um pool de threads para controlar a prioridade e o isolamento do thread, criar características personalizadas e, possivelmente, melhorar a capacidade de resposta. No entanto, pools de threads adicionais exigem mais recursos do sistema (threads, memória do kernel). Muitos pools aumentam o potencial de contenção de CPU.

  • Se possível, use um objeto de espera em vez de um mecanismo baseado em APC para sinalizar um thread de pool de threads. As APCs não funcionam tão bem com threads do pool de threads quanto outros mecanismos de sinalização porque o sistema controla o tempo de vida dos threads do pool de threads, portanto, é possível que um thread seja encerrado antes que a notificação seja entregue.

  • Use a extensão do depurador do pool de threads, !tp. Esse comando tem o seguinte uso:

    • sinalizadores de endereçodo pool
    • sinalizadores de endereço obj
    • sinalizadores de endereço tqueue
    • endereço do garçom
    • endereço de trabalho

    Para pool, garçom e trabalho, se o endereço for zero, o comando despejará todos os objetos. Para garçom e trabalhador, omitir o endereço despeja o thread atual. Os seguintes sinalizadores são definidos: 0x1 (saída de linha única), 0x2 (membros de despejo) e 0x4 (fila de trabalho do pool de despejo).

API do Pool de Threads

Usando as funções do pool de threads