Compartilhar via


A E/S de disco assíncrona aparece como síncrona no Windows

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

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

Resumo

A E/S de 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 é considerada 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 sobreposta é frequentemente usado para E/S assíncrona e E/S não sobreposta para E/S síncrona. Este artigo usa os termos Assíncrono e Síncrono para operações de E/S. Este artigo pressupõe que o leitor tenha familiaridade com as funções de E/S de arquivo, como CreateFile, ReadFile, WriteFile.

Freqüentemente, as operações de E/S assíncronas se comportam da mesma forma que as E/S síncronas. Determinadas condições que este artigo discute nas seções posteriores, o que torna as operações de E/S 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 seja concluída.

Várias funções estão relacionadas a E/S síncronas e assíncronas. Este artigo usa ReadFile e WriteFile como exemplos. Boas alternativas seriam ReadFileEx e WriteFileEx. Embora este artigo discuta apenas a E/S de 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 for aberto. Esse sinalizador permite que as operações de E/S no arquivo sejam feitas de forma assíncrona. Este é 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 E/S assíncrona porque o sistema se reserva o direito de tornar uma operação síncrona se necessário. Portanto, é melhor se você escrever o programa para lidar corretamente com uma operação de E/S que pode 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 trabalho 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 tornada assíncrona, então GetOverlappedResult é usado para determinar o número real de bytes transferidos na operação após sua conclusão. O &NumberOfBytesRead passado para ReadFile não tem sentido.

Por outro lado, se uma operação for concluída imediatamente, então &NumberOfBytesRead passada para ReadFile é 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á ter dados corrompidos. Além disso, você deve inicializá-lo corretamente para que nenhum dado restante afete a nova operação antes de 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 seja concluída; Ler ou gravar 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á vários motivos pelos quais as operações de E/S são concluídas de forma síncrona, mesmo que você tenha codificado para operação assíncrona.

Compactação

Uma obstrução à operação assíncrona é a compactação do 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 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

Outro motivo pelo 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, em seguida, emitindo um WriteFilearquivo .

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 preenchê-los com zero.

Como o sistema de arquivos NTFS não preenche os dados com zero até o comprimento de dados válido (VDL) definido por SetFileValidData, essa função tem implicações de segurança em que o arquivo pode receber clusters que foram ocupados anteriormente por outros arquivos. Portanto, SetFileValidData requer que o chamador tenha o novo SeManageVolumePrivilege habilitado (por padrão, isso é atribuído apenas aos administradores). A Microsoft recomenda que os ISVs (fornecedores independentes de software) 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 no 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 arquivo. O gerenciador de memória no Windows não fornece um mecanismo de falha de página assíncrona para gerenciar os mapeamentos de arquivo 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 ReadFile a 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 para o 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 serão feitas de forma síncrona.

O comportamento do gerenciador de cache também pode ser influenciado com base no acesso a um arquivo sequencial ou aleatoriamente. Os benefícios do cache são mais vistos 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 maneira aleatória, use o FILE_FLAG_RANDOM_ACCESS sinalizador para CreateFile 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 para usar esse 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 de testes do mundo real

Veja a seguir 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 entre si 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 outro trabalho 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 gastou 4,495880 segundos chamando ReadFile para concluir suas operações, mas o teste 1 gastou 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. Esse 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 rápido do que as leituras assíncronas do cache. Esse 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 padrão ao arquivo sem especificar nenhum sinalizador especial é CreateFile 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 antecipada assíncrona preditiva e gravação lenta assíncrona 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 seu aplicativo não for simples, talvez seja necessário fazer algum monitoramento de perfil e desempenho para determinar o melhor método, semelhante aos testes ilustrados anteriormente neste artigo. É útil criar o perfil do tempo gasto na ReadFile função ou WriteFile e, em seguida, comparar esse tempo com o tempo necessário para que as operações de E/S reais sejam concluídas. Se a maior parte do tempo for gasta na emissão da 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 estarão sendo 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 com que seu programa está 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 rastreamento dos contadores de desempenho para os objetos Disco Físico ou Disco Lógico indicará o desempenho dos sistemas de disco.

Existem 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, você deve primeiro emitir o DiskPerf comando. Depois de emitir o comando, você deve reiniciar o sistema para iniciar a coleta de dados.

Referências

E/S síncrona e assíncrona