Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
As portas de conclusão de E/S fornecem um modelo de threading eficiente para processar várias solicitações de E/S assíncronas em um sistema multiprocessador. Quando um processo cria uma porta de conclusão de E/S, o sistema cria um objeto de fila associado para threads cuja única finalidade é atender a essas solicitações. Os processos que lidam com muitas solicitações de E/S assíncronas simultâneas podem fazer isso de forma mais rápida e eficiente usando portas de conclusão de E/S em conjunto com um pool de threads pré-alocado do que criando threads no momento em que recebem uma solicitação de E/S.
Quando usar portas de conclusão de E/S
| Scenario | Abordagem recomendada |
|---|---|
| Servidor de alto desempenho que manipula centenas/milhares de conexões simultâneas | Portas de conclusão de E/S — criadas especificamente para isso. O kernel gerencia o escalonamento de threads para corresponder à concorrência da CPU. |
| Simultaneidade moderada (dezenas de operações assíncronas) |
E/S de pool de threads (CreateThreadpoolIo) — API mais simples, gerencia o IOCP internamente. Prefira um novo código que não precise de controle de thread manual. |
| Operações de arquivo assíncronas simples no C++ moderno | Corrotinas C++20 com um despachante IOCP personalizado ou .NET FileStream com async/await. |
| E/S de thread única ou de baixo volume | E/S síncrona ou E/S sobreposta simples com sinalização de evento. O IOCP adiciona complexidade desnecessária para cenários de fluxo único. |
Nota
API do pool de threads versus IOCP bruto: a API do pool de threads Windows (CreateThreadpoolIo, StartThreadpoolIo) usa o IOCP internamente, mas manipula automaticamente o gerenciamento do ciclo de vida do thread. Para novos aplicativos de servidor, considere a API do pool de threads primeiro – ela fornece a mesma escalabilidade com menos código clichê. Use IOCP bruto quando precisar de controle explícito sobre o valor de concorrência da porta de conclusão ou gerenciamento de threads personalizado.
Como funcionam as portas de conclusão de E/S
A função CreateIoCompletionPort cria uma porta de conclusão de E/S e associa um ou mais identificadores de arquivo a essa porta. Quando uma operação de E/S assíncrona em um desses identificadores de arquivo é concluída, um pacote de conclusão de E/S é enfileirado em ordem FIFO (primeiro a entrar, primeiro a sair) na porta de conclusão de E/S associada. Um uso importante desse mecanismo é combinar o ponto de sincronização de vários handles de arquivo em um único objeto, embora também existam outras aplicações úteis. Observe que, embora os pacotes sejam enfileirados na ordem FIFO, eles podem ser desenfileirados em ordem diferente.
Nota
O termo identificador de arquivo conforme usado aqui refere-se a uma abstração do sistema que representa um ponto de extremidade de E/S sobreposto, não apenas um arquivo em disco. Por exemplo, pode ser uma extremidade de rede, um soquete TCP, um canal nomeado ou um compartimento de correio. Qualquer objeto do sistema que oferece suporte a E/S sobreposta pode ser usado. Para obter uma lista de funções de E/S relacionadas, consulte o final deste tópico.
Quando um identificador de arquivo estiver associado a uma porta de conclusão, o bloco de status passado não será atualizado até que o pacote seja removido da porta de conclusão. A única exceção é se a operação original retornar de forma síncrona com um erro. Uma thread (seja criada pela thread principal ou a própria thread principal) usa a função GetQueuedCompletionStatus para aguardar que um pacote de conclusão seja enfileirado na porta de conclusão de E/S, em vez de esperar diretamente pela conclusão da E/S assíncrona. As threads que bloqueiam sua execução em uma porta de conclusão de E/S são liberadas na ordem LIFO (último a entrar, primeiro a sair), e o próximo pacote de conclusão é retirado da fila FIFO da porta de conclusão de E/S para essa thread. Isso significa que, quando um pacote de conclusão é liberado para uma thread, o sistema libera a última (mais recente) thread associada a essa porta, passando a ela as informações de conclusão da E/S mais antiga.
Embora qualquer número de threads possa chamar GetQueuedCompletionStatus para uma porta de conclusão de E/S especificada, quando um thread especificado chama GetQueuedCompletionStatus pela primeira vez, ele se torna associado à porta de conclusão de E/S especificada até que uma das três coisas ocorra: o thread sai, especifica uma porta de conclusão de E/S diferente ou fecha a porta de conclusão de E/S. Em outras palavras, uma única thread pode ser associada a, no máximo, uma porta de conclusão de E/S.
Quando um pacote de conclusão é enfileirado em uma porta de conclusão de E/S, o sistema primeiro verifica quantas threads associadas a essa porta estão em execução. Se o número de threads em execução for menor que o valor de simultaneidade (discutido na próxima seção), um dos threads de espera (o mais recente) poderá processar o pacote de conclusão. Quando um thread em execução conclui seu processamento, ele normalmente chama GetQueuedCompletionStatus novamente, momento em que ele retorna com o próximo pacote de conclusão ou aguarda se a fila está vazia.
As threads podem usar a função PostQueuedCompletionStatus para colocar pacotes de conclusão na fila de uma porta de conclusão de E/S. Dessa forma, a porta de conclusão pode ser usada para receber comunicações de outras threads do processo, além de receber pacotes de conclusão de E/S do sistema de E/S. A função PostQueuedCompletionStatus permite que um aplicativo enfileire seus próprios pacotes de conclusão de propósito específico na porta de conclusão de E/S sem iniciar uma operação de E/S assíncrona. Isso é útil para notificar threads de trabalho sobre eventos externos, por exemplo.
O identificador da porta de conclusão de E/S e cada identificador de arquivo associado a essa porta de conclusão de E/S específica são conhecidos como referências à porta de conclusão de E/S. A porta de conclusão de E/S é liberada quando não há mais referências a ela. Portanto, todos esses identificadores devem ser fechados corretamente para liberar a porta de conclusão de E/S e seus recursos de sistema associados. Após essas condições serem satisfeitas, um aplicativo deve fechar o identificador da porta de conclusão de E/S chamando a função CloseHandle.
Nota
Uma porta de conclusão de E/S está associada ao processo que a criou e não pode ser compartilhada entre processos. No entanto, um único identificador pode ser compartilhado entre threads no mesmo processo.
Threads e Concorrência
A propriedade mais importante de uma porta de conclusão de E/S a ser considerada cuidadosamente é o valor de concorrência. O valor de simultaneidade de uma porta de conclusão é definido quando ela é criada com CreateIoCompletionPort por meio do parâmetro NumberOfConcurrentThreads. Esse valor limita o número de threads executáveis associados à porta de conclusão. Quando o número total de threads executáveis associados à porta de conclusão atinge o valor de simultaneidade, o sistema bloqueia a execução de quaisquer threads subsequentes associados a essa porta de conclusão até que o número de threads executáveis fique abaixo do valor de simultaneidade.
O cenário mais eficiente ocorre quando há pacotes de conclusão aguardando na fila, mas nenhuma espera pode ser atendida porque a porta atingiu seu limite de concorrência. Considere o que acontece com um valor de concorrência de um e várias threads aguardando na chamada da função GetQueuedCompletionStatus. Nesse caso, se a fila sempre tiver pacotes de conclusão aguardando, quando a thread em execução chamar GetQueuedCompletionStatus, ela não bloqueará a execução porque, como mencionado anteriormente, a fila de threads é LIFO (último a entrar, primeiro a sair). Em vez disso, esta thread irá processar imediatamente o próximo pacote de conclusão enfileirado. Nenhuma troca de contexto de thread ocorrerá, pois a thread em execução está continuamente processando pacotes de conclusão e as outras threads não conseguem executar.
Nota
No exemplo anterior, as threads extras parecem ser inúteis e nunca ser executadas, mas isso pressupõe que a thread em execução nunca seja colocada em um estado de espera por outro mecanismo, termine ou de alguma outra forma feche sua porta de conclusão de E/S associada. Considere todas essas ramificações de execução de thread ao projetar o aplicativo.
O melhor valor máximo geral a ser escolhido para o valor de simultaneidade é o número de CPUs no computador. Se a transação exigir uma computação longa, um valor de simultaneidade maior permitirá que mais threads sejam executados. Cada pacote de conclusão pode levar mais tempo para ser concluído, mas mais pacotes de conclusão serão processados ao mesmo tempo. Você pode experimentar o valor de concorrência em conjunto com ferramentas de análise de desempenho para alcançar o melhor resultado para o seu aplicativo.
O sistema também permite que um thread aguardando em GetQueuedCompletionStatus processe um pacote de conclusão se outro thread em execução associado à mesma porta de conclusão de E/S entrar em um estado de espera por outros motivos, por exemplo, a função SuspendThread . Quando o thread no estado de espera começa a ser executado novamente, pode haver um breve período em que o número de threads ativos excede o valor de simultaneidade. No entanto, o sistema reduz rapidamente esse número não permitindo novos threads ativos até que o número de threads ativos fique abaixo do valor de simultaneidade. Este é um dos motivos para que seu aplicativo crie mais threads em seu pool de threads do que o valor de concorrência. O gerenciamento do pool de threads está além do escopo deste tópico, mas uma boa regra geral é ter um mínimo de duas vezes mais threads no pool de threads do que há processadores no sistema. Para obter informações adicionais sobre o pool de threads, consulte Pools de Threads.
Funções de E/S com suporte
As funções a seguir podem ser usadas para iniciar operações de E/S que são concluídas usando portas de conclusão de E/S. Você deve passar à função uma instância da estrutura OVERLAPPED e um identificador de arquivo anteriormente associado a uma porta de conclusão de E/S (por uma chamada para CreateIoCompletionPort) para habilitar o mecanismo de porta de conclusão de E/S:
- AcceptEx
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- ReadFile
- TransactNamedPipe
- WaitCommEvent
- WriteFile
- WSASendMsg
- WSASendTo
- WSASend
- WSARecvFrom
- LPFN_WSARECVMSG (WSARecvMsg)
- WSARecv