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 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.

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

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 DESP (primeiro a entrar em primeiro a sair) 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 haja outros aplicativos úteis. Observe que, embora os pacotes sejam enfileirados na ordem FIFO, eles podem ser removidos da fila em uma ordem diferente.

Observação

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 um ponto de extremidade de rede, soquete TCP, pipe nomeado ou slot de email. Qualquer objeto do sistema que dê 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 será se a operação original retornar de forma síncrona com um erro. Um thread (criado pelo thread main ou pelo próprio thread main) usa a função GetQueuedCompletionStatus para aguardar a conclusão de um pacote de conclusão na 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 (último a entrar primeiro a sair) e o próximo pacote de conclusão é extraído 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 é encerrado, 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 em 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 que o valor de simultaneidade (discutido na próxima seção), um dos threads em 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.

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 enfileira seus próprios pacotes de conclusão de finalidade especial para a 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 de 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 devidamente fechados para liberar a porta de conclusão de E/S e seus recursos de sistema associados. Depois que essas condições forem atendidas, um aplicativo deverá fechar o identificador de 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 os processos. No entanto, um único identificador é compartilhável entre threads no mesmo processo.

 

Threads e simultaneidade

A propriedade mais importante de uma porta de conclusão de E/S a ser considerada com cuidado é o valor de simultaneidade. O valor de simultaneidade de uma porta de conclusão é especificado quando ele é criado 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 simultaneidade. Considere o que acontece com um valor de simultaneidade de um e vários threads aguardando na chamada da 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, conforme mencionado anteriormente, a fila de threads é LIFO. Em vez disso, esse thread selecionará imediatamente o próximo pacote de conclusão enfileirado. Nenhuma opção de contexto de thread ocorrerá, porque o thread em execução está continuamente selecionando 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 é colocado em um estado de espera por algum outro mecanismo, termina ou fecha 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 simultaneidade em conjunto com as 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 fique abaixo do valor de simultaneidade. Esse é um dos motivos 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 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 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:

Sobre Processos e Threads

BindIoCompletionCallback

Createiocompletionport

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus