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 aplicativos e fornecer gerenciamento dos threads de trabalho. Os aplicativos podem enfileirar itens de trabalho, associar trabalho a identificadores de espera, enfileirar automaticamente com base em um temporizador e vincular com E/S.
Arquitetura do pool de threads
Os seguintes aplicativos podem se beneficiar do uso de um pool de threads:
- Um aplicativo que é altamente paralelo e pode despachar 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 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 opções de contexto.
- Um aplicativo que cria threads de garçom personalizados para aguardar eventos.
O pool de threads original foi completamente rearquitetado no Windows Vista. O novo pool de threads foi aprimorado porque fornece um único tipo de thread de trabalho (suporta 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 que são 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 aguardam em várias alças 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
Boas Práticas
A nova API de pool de threads fornece mais flexibilidade e controle do que a API de pool de threads original . No entanto, existem algumas diferenças sutis, mas importantes. Na API original, a redefinição de espera era automática; na nova API, a espera deve ser explicitamente redefinida a cada vez. A API original manipulava 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.
A seguir estão 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á lidando com 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 de 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 aCancelSynchronousIoou função 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 as alças de espera e seus objetos associados ativos até que o pool de threads tenha sinalizado que terminou com a alça.
Marque todos os threads que estão aguardando em 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 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 da execução de retornos de chamada.
Evite impasses eliminando dependências entre itens de trabalho e entre retornos de chamada, garantindo que um retorno de chamada não esteja esperando ser concluído e preservando a prioridade do thread.
Não enfileire 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 um máximo de 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 do que o número de processadores.
Evite o modelo de apartamento COM single-threaded, pois é incompatível com o pool de threads. O STA cria o estado do thread que pode afetar o próximo item de trabalho do thread. O STA é geralmente de longa duração e tem afinidade com roscas, que é o oposto do pool de fios.
Crie um novo 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). O excesso de pools aumenta o potencial de contenção da CPU.
Se possível, use um objeto waitable em vez de um mecanismo baseado em APC para sinalizar um thread de pool de threads. Os APCs não funcionam tão bem com threads de 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:
- pool endereçosinalizadores
- obj endereçobandeiras
- tqueue endereçosinalizadores
- Garçom endereço
- endereço trabalhador
Para pool, garçom e trabalhador, 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).
Tópicos relacionados