Partilhar via


Portas de conclusão de E/S

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.

Como funcionam as portas de conclusão de E/S

A funçãoCreateIoCompletionPort 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 na ordem FIFO (first-in-first-out) para a porta de conclusão de E/S associada. Um uso poderoso para esse mecanismo é combinar o ponto de sincronização para vários identificadores de arquivo em um único objeto, embora também existam outros aplicativos ú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 um ponto de extremidade de rede, soquete TCP, pipe nomeado ou slot de email. Qualquer objeto de sistema que suporte 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 é 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 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, um único thread pode ser associado a, no máximo, 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 ele 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 de outros 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 para fins especiais 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 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 de porta de conclusão de E/S chamando a funçãoCloseHandle.

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, uma única alça é compartilhável entre threads 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 simultaneidade de uma porta de conclusão é especificado 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 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 com um valor de simultaneidade de um e vários threads aguardando na chamada de funçãoGetQueuedCompletionStatus. 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. Nenhuma opção de contexto de thread ocorrerá, porque o thread em execução está continuamente pegando pacotes de conclusão e os outros threads não podem ser executados.

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çãoSuspendThread. 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 pool 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 concluídas usando portas de conclusão de E/S. Você deve passar à função uma instância da estrutura deOVERLAPPEDe um identificador de arquivo previamente 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:

Sobre processos e threads

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus