Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. 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 fazê-lo de forma mais rápida e eficiente usando portas de conclusão de E/S em conjunto com um pool de threads pré-alocados 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 a lidar com centenas/milhares de ligações simultâneas | Portas de conclusão de I/O — desenhadas especificamente para isto. O kernel gestiona o escalonamento de threads de acordo com o paralelismo do CPU. |
| Concorrência moderada (dezenas de operações assíncronas) |
E/S do pool de threads (CreateThreadpoolIo) — API mais simples, gere o IOCP internamente. Prefiro código novo que não precise de controlo manual da thread. |
| Operações simples de ficheiros assíncronos em C++ moderno | Corrotinas C++20 com um distribuidor IOCP personalizado, ou .NET FileStream com async/await. |
| Entrada/Saída de um único thread ou de baixo volume | E/S síncrona ou E/S sobreposta simples com sinalização de eventos. O IOCP acrescenta complexidade desnecessária para cenários de fluxo único. |
Observação
API do pool de threads vs IOCP bruto: A API do pool de threads do Windows (CreateThreadpoolIo, StartThreadpoolIo) usa o IOCP internamente mas gere automaticamente o ciclo de vida do thread. Para novas aplicações de servidor, considere primeiro a API do pool de threads — oferece a mesma escalabilidade com menos código padrão. Utilize IOCP em bruto quando precisar de controlo explícito sobre o nível de concorrência da porta de conclusão ou de gestão personalizada de threads.
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 num destes identificadores de ficheiro fica concluída, um pacote de conclusão de E/S é colocado em fila por ordem de chegada (FIFO, first-in-first-out) na porta de conclusão de E/S associada. Uma utilização importante deste mecanismo consiste em combinar o ponto de sincronização de vários descritores de ficheiro num único objeto, embora também existam outras aplicações úteis. Por favor, note que, enquanto os pacotes estão enfileirados na ordem FIFO, eles podem ser retirados da fila em uma ordem diferente.
Observação
O termo identificador de arquivo 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 no disco. Por exemplo, pode ser uma extremidade de rede, um socket TCP, um canal com nome ou uma caixa de correio. Qualquer objeto do sistema que suporte E/S assíncrona pode ser utilizado. Para obter uma lista de funções de E/S relacionadas, consulte o final deste tópico.
Quando um identificador de arquivo é 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. Um thread (criado pelo thread principal ou pelo próprio thread principal) usa a funçãoGetQueuedCompletionStatuspara aguardar que um pacote de conclusão seja enfileirado para a porta de conclusão de E/S, em vez de aguardar diretamente a conclusão da E/S assíncrona. Os threads que bloqueiam sua execução em uma porta de conclusão de E/S são liberados na ordem LIFO (last-in-first-out), e o próximo pacote de conclusão é retirado da fila FIFO da porta de conclusão de E/S para esse thread. Isso significa que, quando um pacote de conclusão é liberado para um thread, o sistema libera o último thread (mais recente) associado a essa porta, passando-lhe as informações de conclusão para a conclusão de 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, no máximo, a uma porta de conclusão de E/S.
Quando um pacote de conclusão é enfileirado para uma porta de conclusão de E/S, o sistema primeiro verifica quantos threads associados a essa porta estão em execução. Se o número de threads em execução for menor do que o valor de simultaneidade (discutido na próxima seção), um dos threads em espera (o mais recente) terá permissão para processar o pacote de conclusão. Quando um thread em execução conclui seu processamento, ele normalmente chama GetQueuedCompletionStatus novamente, momento em que retorna com o próximo pacote de conclusão ou aguarda se a fila estiver vazia.
Os threads podem usar a função PostQueuedCompletionStatus para colocar pacotes de conclusão na fila de uma porta de conclusão de E/S. Ao fazer isso, a porta de conclusão pode ser usada para receber comunicações provenientes de outros encadeamentos do processo, além de pacotes de conclusão de E/S provenientes do sistema de E/S. A função PostQueuedCompletionStatus permite que uma aplicação enfileire os seus próprios pacotes de conclusão de propósito especial numa porta de conclusão de E/S sem iniciar uma operação de E/S assíncrona. Isso é útil para notificar threads de trabalho de 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. Depois que essas condições forem satisfeitas, um aplicativo deve fechar o identificador da porta de conclusão de E/S chamando a função CloseHandle .
Observação
Uma porta de conclusão de E/S está associada ao processo que a criou e não é compartilhável entre processos. No entanto, um único identificador pode ser partilhado entre linhas de execução no mesmo processo.
Tópicos e simultaneidade
A propriedade mais importante de uma porta de conclusão de E/S a considerar cuidadosamente é o valor da simultaneidade. O valor de concorrência de uma porta de conclusão é especificado quando ela é criada com CreateIoCompletionPort através 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 caia 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 satisfeita porque a porta atingiu seu limite de simultaneidade. Considere o que acontece quando o valor de concorrência é um, com múltiplas threads aguardando na chamada de função GetQueuedCompletionStatus. Nesse caso, se a fila sempre tiver pacotes de conclusão aguardando, quando o thread em execução chamar GetQueuedCompletionStatus, ele não bloqueará a execução porque, como mencionado anteriormente, a fila de threads é LIFO. Em vez disso, esse thread pegará imediatamente o próximo pacote de conclusão em fila. Não ocorrerão mudanças de contexto de threads, porque a thread em execução está continuamente a processar pacotes de conclusão e as outras threads não conseguem executar.
Observação
No exemplo anterior, os threads extras parecem ser inúteis e nunca são executados, mas isso pressupõe que o thread em execução nunca seja colocado em um estado de espera por algum outro mecanismo, encerre ou feche de outra forma 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 sua transação exigiu um cálculo longo, um valor de simultaneidade maior permitirá que mais threads sejam executados. Cada pacote de conclusão pode levar mais tempo para terminar, mas mais pacotes de conclusão serão processados ao mesmo tempo. Você pode experimentar o valor de simultaneidade em conjunto com ferramentas de criação de perfil para obter o melhor efeito para 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 caia abaixo do valor de simultaneidade. Esse é um motivo para que seu aplicativo crie mais threads em seu pool de threads do que o valor de simultaneidade. 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 os processadores no sistema. Para obter informações adicionais sobre agrupamento de threads, consulte Thread Pools.
Funções de E/S suportadas
As funções a seguir podem ser usadas para iniciar operações de E/S que são concluídas através de portas de conclusão de E/S. Você deve passar para a função uma instância da estrutura OVERLAPPED e um identificador de arquivo previamente associado a uma porta de conclusão de I/O (por uma chamada para CreateIoCompletionPort) para habilitar o mecanismo de porta de conclusão de I/O:
- AcceptEx
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- ReadFile
- TransactNamedPipe
- WaitCommEvent
- WriteFile
- WSASendMsg
- WSASendTo
- WSASend
- WSARecvFrom
- LPFN_WSARECVMSG (WSARecvMsg)
- WSARecv