E/S de disco assíncrono é exibido como síncrono no Windows

Este artigo ajuda você a resolve o problema em que o comportamento padrão para E/S é síncrono, mas ele aparece como assíncrono.

Versão original do produto: Windows
Número de KB original: 156932

Resumo

A E/S do arquivo no Microsoft Windows pode ser síncrona ou assíncrona. O comportamento padrão para E/S é síncrono, em que uma função de E/S é chamada e retorna quando a E/S é concluída. A E/S assíncrona permite que uma função de E/S retorne a execução de volta ao chamador imediatamente, mas a E/S não deve ser concluída até algum momento futuro. O sistema operacional notifica o chamador quando a E/S é concluída. Em vez disso, o chamador pode determinar o status da operação de E/S pendente usando serviços do sistema operacional.

A vantagem da E/S assíncrona é que o chamador tem tempo para fazer outro trabalho ou emitir mais solicitações enquanto a operação de E/S está sendo concluída. O termo E/S sobreposto é frequentemente usado para E/S assíncrono e E/S não sobreposto para E/S síncrono. Este artigo usa os termos Operações assíncronas e síncronas para operações de E/S. Este artigo pressupõe que o leitor tenha familiaridade com as funções de E/S do Arquivo, como CreateFile, ReadFile, WriteFile.

Com frequência, as operações de E/S assíncronas se comportam da mesma forma que a E/S síncrona. Certas condições que este artigo discute nas seções posteriores, o que faz com que as operações de E/S sejam concluídas de forma síncrona. O chamador não tem tempo para trabalho em segundo plano porque as funções de E/S não retornam até que a E/S esteja concluída.

Várias funções estão relacionadas à E/S síncrona e assíncrona. Este artigo usa ReadFile e WriteFile como exemplos. Boas alternativas seriam ReadFileEx e WriteFileEx. Embora este artigo discuta apenas a E/S do disco especificamente, muitos dos princípios podem ser aplicados a outros tipos de E/S, como E/S serial ou E/S de rede.

Configurar E/S assíncrona

O FILE_FLAG_OVERLAPPED sinalizador deve ser especificado quando CreateFile o arquivo é aberto. Esse sinalizador permite que as operações de E/S no arquivo sejam feitas de forma assíncrona. Veja um exemplo:

HANDLE hFile;

hFile = CreateFile(szFileName,
                      GENERIC_READ,
                      0,
                      NULL,
                      OPEN_EXISTING,
                      FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
                      NULL);

if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();

Tenha cuidado ao codificar para E/S assíncrona, pois o sistema se reserva o direito de tornar uma operação síncrona, se necessário. Portanto, é melhor que você escreva o programa para manipular corretamente uma operação de E/S que possa ser concluída de forma síncrona ou assíncrona. O código de exemplo demonstra essa consideração.

Há muitas coisas que um programa pode fazer enquanto aguarda a conclusão de operações assíncronas, como enfileirar operações adicionais ou fazer trabalhos em segundo plano. Por exemplo, o código a seguir lida corretamente com a conclusão sobreposta e não sobreposta de uma operação de leitura. Ele não faz nada mais do que esperar que a E/S pendente seja concluída:

if (!ReadFile(hFile,
               pDataBuf,
               dwSizeOfBuffer,
               &NumberOfBytesRead,
               &osReadOperation )
{
   if (GetLastError() != ERROR_IO_PENDING)
   {
      // Some other error occurred while reading the file.
      ErrorReadingFile();
      ExitProcess(0);
   }
   else
      // Operation has been queued and
      // will complete in the future.
      fOverlapped = TRUE;
}
else
   // Operation has completed immediately.
   fOverlapped = FALSE;

if (fOverlapped)
{
   // Wait for the operation to complete before continuing.
   // You could do some background work if you wanted to.
   if (GetOverlappedResult( hFile,
                           &osReadOperation,
                           &NumberOfBytesTransferred,
                           TRUE))
      ReadHasCompleted(NumberOfBytesTransferred);
   else
      // Operation has completed, but it failed.
      ErrorReadingFile();
}
else
   ReadHasCompleted(NumberOfBytesRead);

Observação

&NumberOfBytesRead passado para ReadFile é diferente de &NumberOfBytesTransferred passado para GetOverlappedResult. Se uma operação tiver sido feita assíncrona, ela GetOverlappedResult será usada para determinar o número real de bytes transferidos na operação após a conclusão. O &NumberOfBytesRead passado para ReadFile não tem sentido.

Por outro lado, se uma operação for concluída imediatamente, a &NumberOfBytesRead passagem ReadFile será válida para o número de bytes lidos. Nesse caso, ignore a OVERLAPPED estrutura passada para ReadFile; não a use com GetOverlappedResult ou WaitForSingleObject.

Outra ressalva com a operação assíncrona é que você não deve usar uma OVERLAPPED estrutura até que sua operação pendente seja concluída. Em outras palavras, se você tiver três operações de E/S pendentes, deverá usar três OVERLAPPED estruturas. Se você reutilizar uma OVERLAPPED estrutura, receberá resultados imprevisíveis nas operações de E/S e poderá sofrer corrupção de dados. Além disso, você deve inicializá-la corretamente para que nenhum dado de sobra afete a nova operação antes de poder usar uma OVERLAPPED estrutura pela primeira vez ou antes de reutilizá-la após a conclusão de uma operação anterior.

O mesmo tipo de restrição se aplica ao buffer de dados usado em uma operação. Um buffer de dados não deve ser lido ou gravado até que sua operação de E/S correspondente tenha sido concluída; ler ou escrever o buffer pode causar erros e dados corrompidos.

A E/S assíncrona ainda parece ser síncrona

No entanto, se você seguiu as instruções anteriores neste artigo, todas as operações de E/S ainda são normalmente concluídas de forma síncrona na ordem emitida, e nenhuma das operações retorna FALSE com GetLastError() o retorno ERROR_IO_PENDING, o ReadFile que significa que você não tem tempo para nenhum trabalho em segundo plano. Por que isso ocorre?

Há uma série de razões pelas quais as operações de E/S são concluídas de forma síncrona, mesmo que você tenha codificado para uma operação assíncrona.

Compressão

Uma obstrução à operação assíncrona é a compactação NTFS (New Technology File System). O driver do sistema de arquivos não acessará arquivos compactados de forma assíncrona; Em vez disso, todas as operações são feitas síncronas. Essa obstrução não se aplica a arquivos compactados com utilitários semelhantes a COMPRESS ou PKZIP.

Criptografia NTFS

Semelhante à compactação, a criptografia de arquivo faz com que o driver do sistema converta E/S assíncrona em síncrona. Se os arquivos forem descriptografados, as solicitações de E/S serão assíncronas.

Estender um arquivo

Outra razão pela qual as operações de E/S são concluídas de forma síncrona são as próprias operações. No Windows, qualquer operação de gravação em um arquivo que estenda seu comprimento será síncrona.

Observação

Os aplicativos podem tornar a operação de gravação mencionada anteriormente assíncrona alterando o Comprimento de Dados Válido do arquivo usando a SetFileValidData função e emitindo um WriteFile.

Usando SetFileValidData (que está disponível no Windows XP e versões posteriores), os aplicativos podem estender arquivos com eficiência sem incorrer em uma penalidade de desempenho por preenchimento zero.

Como o sistema de arquivos NTFS não preenche zero os dados até o VDL (comprimento de dados válido) definido por SetFileValidData, essa função tem implicações de segurança em que o arquivo pode ser atribuído clusters que foram ocupados anteriormente por outros arquivos. Portanto, SetFileValidData exige que o chamador tenha o novo SeManageVolumePrivilege habilitado (por padrão, isso é atribuído apenas aos administradores). A Microsoft recomenda que os ISVs (fornecedores de software independentes) considerem cuidadosamente as implicações do uso dessa função.

Cache

A maioria dos drivers de E/S (disco, comunicações e outros) tem um código de caso especial em que, se uma solicitação de E/S puder ser concluída imediatamente, a operação será concluída e a ReadFile função ou WriteFile retornará TRUE. De todas as maneiras, esses tipos de operações parecem ser síncronos. Para um dispositivo de disco, normalmente, uma solicitação de E/S pode ser concluída imediatamente quando os dados são armazenados em cache na memória.

Os dados não estão em cache

No entanto, o esquema de cache pode funcionar contra você se os dados não estiverem no cache. O cache do Windows é implementado internamente usando mapeamentos de arquivos. O gerenciador de memória no Windows não fornece um mecanismo de falha de página assíncrono para gerenciar os mapeamentos de arquivos usados pelo gerenciador de cache. O gerenciador de cache pode verificar se a página solicitada está na memória, portanto, se você emitir uma leitura em cache assíncrona e as páginas não estiverem na memória, o driver do sistema de arquivos pressupõe que você não deseja que seu thread seja bloqueado e a solicitação será tratada por um pool limitado de threads de trabalho. O controle é retornado ao seu programa após sua ReadFile chamada com a leitura ainda pendente.

Isso funciona bem para um pequeno número de solicitações, mas como o pool de threads de trabalho é limitado (atualmente três em um sistema de 16 MB), ainda haverá apenas algumas solicitações enfileiradas no driver de disco em um determinado momento. Se você emitir várias operações de E/S para dados que não estão no cache, o gerenciador de cache e o gerenciador de memória ficarão saturados e suas solicitações ficarão síncronas.

O comportamento do gerenciador de cache também pode ser influenciado com base em se você acessa um arquivo sequencialmente ou aleatoriamente. Os benefícios do cache são vistos mais ao acessar arquivos sequencialmente. O FILE_FLAG_SEQUENTIAL_SCAN sinalizador na CreateFile chamada otimizará o cache para esse tipo de acesso. No entanto, se você acessar arquivos de forma aleatória, use o FILE_FLAG_RANDOM_ACCESS sinalizador CreateFile para instruir o gerenciador de cache a otimizar seu comportamento para acesso aleatório.

Não use o cache

O FILE_FLAG_NO_BUFFERING sinalizador tem o maior efeito sobre o comportamento do sistema de arquivos para operação assíncrona. É a melhor maneira de garantir que as solicitações de E/S sejam assíncronas. Ele instrui o sistema de arquivos a não usar nenhum mecanismo de cache.

Observação

Há algumas restrições ao uso desse sinalizador que tem a ver com o alinhamento do buffer de dados e o tamanho do setor do dispositivo. Para obter mais informações, consulte a referência de função na documentação da função CreateFile sobre como usar esse sinalizador corretamente.

Resultados do teste do mundo real

Os resultados a seguir são alguns resultados de teste do código de exemplo. A magnitude dos números não é importante aqui e varia de computador para computador, mas a relação dos números em comparação uns com os outros ilumina o efeito geral dos sinalizadores no desempenho.

Você pode esperar ver resultados semelhantes a um dos seguintes:

  • Teste 1

    Asynchronous, unbuffered I/O:  asynchio /f*.dat /n
    Operations completed out of the order in which they were requested.
       500 requests queued in 0.224264 second.
       500 requests completed in 4.982481 seconds.
    

    Este teste demonstra que o programa mencionado anteriormente emitiu 500 solicitações de E/S rapidamente e teve muito tempo para fazer outros trabalhos ou emitir mais solicitações.

  • Teste 2

    Synchronous, unbuffered I/O: asynchio /f*.dat /s /n
        Operations completed in the order issued.
        500 requests queued and completed in 4.495806 seconds.
    

    Este teste demonstra que este programa passou 4,495880 segundos chamando ReadFile para concluir suas operações, mas o teste 1 passou apenas 0,224264 segundos para emitir as mesmas solicitações. No teste 2, não houve tempo extra para o programa fazer qualquer trabalho em segundo plano.

  • Teste 3

    Asynchronous, buffered I/O: asynchio /f*.dat
        Operations completed in the order issued.
        500 requests issued and completed in 0.251670 second.
    

    Este teste demonstra a natureza síncrona do cache. Todas as leituras foram emitidas e concluídas em 0,251670 segundo. Em outras palavras, as solicitações assíncronas foram concluídas de forma síncrona. Este teste também demonstra o alto desempenho do gerenciador de cache quando os dados estão no cache.

  • Teste 4

    Synchronous, buffered I/O: asynchio /f*.dat /s
        Operations completed in the order issued.
        500 requests and completed in 0.217011 seconds.
    

    Este teste demonstra os mesmos resultados do teste 3. As leituras síncronas do cache são concluídas um pouco mais rapidamente do que as leituras assíncronas do cache. Este teste também demonstra o alto desempenho do gerenciador de cache quando os dados estão no cache.

Conclusão

Você pode decidir qual método é melhor porque tudo depende do tipo, tamanho e número de operações que seu programa executa.

O acesso de arquivo padrão sem especificar sinalizadores CreateFile especiais é uma operação síncrona e armazenada em cache.

Observação

Você obtém algum comportamento assíncrono automático nesse modo porque o driver do sistema de arquivos faz leitura assíncrona preditiva e gravação assíncrona e lenta de dados modificados. Embora esse comportamento não torne a E/S do aplicativo assíncrona, é o caso ideal para a grande maioria dos aplicativos simples.

Por outro lado, se o aplicativo não for simples, talvez seja necessário fazer alguns monitoramentos de perfil e desempenho para determinar o melhor método, semelhante aos testes ilustrados anteriormente neste artigo. Criar o perfil do tempo gasto na função ou WriteFile e, em ReadFile seguida, comparar esse tempo com quanto tempo leva para que as operações reais de E/S sejam concluídas é útil. Se a maior parte do tempo for gasto na emissão de E/S, sua E/S será concluída de forma síncrona. No entanto, se o tempo gasto emitindo solicitações de E/S for relativamente pequeno em comparação com o tempo necessário para que as operações de E/S sejam concluídas, suas operações serão tratadas de forma assíncrona. O código de exemplo mencionado anteriormente neste artigo usa a QueryPerformanceCounter função para fazer sua própria criação de perfil interna.

O monitoramento de desempenho pode ajudar a determinar a eficiência do programa usando o disco e o cache. O acompanhamento de qualquer um dos contadores de desempenho do objeto Cache indicará o desempenho do gerenciador de cache. O acompanhamento dos contadores de desempenho dos objetos disco físico ou disco lógico indicará o desempenho dos sistemas de disco.

Há vários utilitários que são úteis no monitoramento de desempenho. PerfMon e DiskPerf são especialmente úteis. Para que o sistema colete dados sobre o desempenho dos sistemas de disco, primeiro você deve emitir o DiskPerf comando. Depois de emitir o comando, você deve reiniciar o sistema para iniciar a coleta de dados.

Referências

E/Síncrono e assíncrono