Pools de threads
Um pool de threads é uma coleção de threads de trabalho que executam retornos de chamada assíncronos com eficiência em nome do aplicativo. O pool de threads é usado principalmente para reduzir o número de threads de aplicativos e fornecer gerenciamento dos threads de trabalho. Os aplicativos podem enfileirar itens de trabalho, associar o trabalho a identificadores aguardáveis, enfileirar automaticamente com base em um temporizador e vincular à E/S.
Arquitetura do pool de threads
Os seguintes aplicativos 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, cada um executado por um curto período. 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 do 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 alternâncias de contexto.
- Um aplicativo que cria threads de espera personalizados para aguardar eventos.
O pool de threads original foi completamente reprojetado no Windows Vista. O novo conjunto de threads foi aprimorado porque fornece um único tipo de thread de trabalho (suporte 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 grupos de limpeza, 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 espera que aguardam vários identificadores de espera
- Uma fila de trabalho
- Um pool de threads padrão para cada processo
- Uma fábrica de trabalhadores que gerencia os threads de trabalho
Práticas Recomendadas
A nova API do pool de threads oferece 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 da espera era automática; na nova API, a espera deve ser redefinida explicitamente a cada vez. A API original tratava 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 diversas 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 do conjunto de threads requer sincronização porque a função de cancelamento pode ser executada em um thread diferente daquele que está manipulando a solicitação de E/S, o que pode resultar no cancelamento de uma operação desconhecida. Para evitar isso, sempre forneça 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. Isso inclui 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 conjunto de threads sinalize que o identificador foi concluído.
Marque todos os threads que estão aguardando operações demoradas (como liberações de E/S ou limpeza de recursos) para que o conjunto de threads possa alocar novos threads em vez de aguardar 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 dos retornos de chamada.
Evite conflitos 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 coloque muitos itens na fila muito rapidamente em um processo com outros componentes usando o conjunto de threads padrão. Há um pool de threads padrão por processo, incluindo Svchost.exe. Por padrão, cada conjunto de threads possui no máximo 500 threads de trabalho. O conjunto 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 COM de thread único, pois ele é incompatível com o pool de threads. STA cria um estado de thread que pode afetar o próximo item de trabalho do thread. STA geralmente tem vida longa e 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 requerem 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 aguardável em vez de um mecanismo baseado em APC para sinalizar um thread do 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. Este comando tem o seguinte uso:
- sinalizadores de endereço de pool
- sinalizadores de endereço de objeto
- sinalizadores de endereço de fila
- endereço do garçom
- endereço do trabalhador
Para pool, garçom e trabalhador, se o endereço for zero, o comando faz dump de todos os objetos. Para garçom e trabalhador, a omissão do endereço despeja o thread atual. Os seguintes sinalizadores são definidos: 0x1 (saída de linha única), 0x2 (membros de dump) e 0x4 (fila de trabalho do pool de dump).
Tópicos relacionados