Asynkron disk-I/O visas som synkron i Windows
Den här artikeln hjälper dig att lösa problemet där standardbeteendet för I/O är synkront, men det visas som asynkront.
Ursprunglig produktversion: Windows
Ursprungligt KB-nummer: 156932
Sammanfattning
Fil-I/O i Microsoft Windows kan vara synkron eller asynkron. Standardbeteendet för I/O är synkront, där en I/O-funktion anropas och returnerar när I/O är klar. Asynkron I/O tillåter att en I/O-funktion returnerar körningen tillbaka till anroparen omedelbart, men I/O antas inte vara klar förrän senare. Operativsystemet meddelar anroparen när I/O är klar. Anroparen kan i stället fastställa statusen för den utestående I/O-åtgärden med hjälp av operativsystemets tjänster.
Fördelen med asynkron I/O är att anroparen har tid att utföra annat arbete eller utfärda fler begäranden medan I/O-åtgärden slutförs. Termen överlappande I/O används ofta för Asynkron I/O och icke-överlappande I/O för synkron I/O. Den här artikeln använder termerna Asynkron och synkron för I/O-åtgärder. Den här artikeln förutsätter att läsaren har kunskaper om fil-I/O-funktioner som CreateFile
, ReadFile
, WriteFile
.
Ofta fungerar asynkrona I/O-åtgärder precis som synkrona I/O. Vissa villkor som beskrivs i den här artikeln i de senare avsnitten, vilket gör att I/O-åtgärderna slutförs synkront. Anroparen har ingen tid för bakgrundsarbete eftersom I/O-funktionerna inte returneras förrän I/O har slutförts.
Flera funktioner är relaterade till synkron och asynkron I/O. Den här artikeln använder ReadFile
och WriteFile
som exempel. Bra alternativ skulle vara ReadFileEx
och WriteFileEx
. Även om den här artikeln endast beskriver disk-I/O specifikt kan många av principerna tillämpas på andra typer av I/O, till exempel seriell I/O eller nätverks-I/O.
Konfigurera asynkron I/O
Flaggan FILE_FLAG_OVERLAPPED
måste anges i CreateFile
när filen öppnas. Med den här flaggan kan I/O-åtgärder i filen utföras asynkront. Här är ett exempel:
HANDLE hFile;
hFile = CreateFile(szFileName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
ErrorOpeningFile();
Var försiktig när du kodar för asynkron I/O eftersom systemet förbehåller sig rätten att göra en åtgärd synkron om det behövs. Det är därför bäst om du skriver programmet för att hantera en I/O-åtgärd som kan slutföras synkront eller asynkront. Exempelkoden visar det här övervägandet.
Det finns många saker som ett program kan göra i väntan på att asynkrona åtgärder ska slutföras, till exempel köa ytterligare åtgärder eller utföra bakgrundsarbete. Följande kod hanterar till exempel överlappande och icke-överlappande slutförande av en läsåtgärd korrekt. Det gör inget annat än att vänta tills den utestående I/O har slutförts:
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);
Kommentar
&NumberOfBytesRead
som skickas till ReadFile
skiljer sig från &NumberOfBytesTransferred
som skickas till GetOverlappedResult
. Om en åtgärd har gjorts asynkron används den GetOverlappedResult
för att fastställa det faktiska antalet byte som överförs i åtgärden när den har slutförts. Det som skickas &NumberOfBytesRead
in ReadFile
är meningslöst.
Om en åtgärd å andra sidan slutförs omedelbart &NumberOfBytesRead
är det ReadFile
giltigt för antalet lästa byte. I det här fallet ignorerar du den struktur som OVERLAPPED
skickas till ReadFile
; använd den inte med GetOverlappedResult
eller WaitForSingleObject
.
En annan varning med asynkron åtgärd är att du inte får använda en OVERLAPPED
struktur förrän dess väntande åtgärd har slutförts. Med andra ord, om du har tre utestående I/O-åtgärder måste du använda tre OVERLAPPED
strukturer. Om du återanvänder en OVERLAPPED
struktur får du oförutsägbara resultat i I/O-åtgärderna och du kan uppleva att data skadas. Dessutom måste du initiera den korrekt så att inga data som lämnas över påverkar den nya åtgärden innan du kan använda en OVERLAPPED
struktur för första gången eller innan du återanvänder den efter att en tidigare åtgärd har slutförts.
Samma typ av begränsning gäller för den databuffert som används i en åtgärd. En databuffert får inte läsas eller skrivas förrän motsvarande I/O-åtgärd har slutförts. läsning eller skrivning av bufferten kan orsaka fel och skadade data.
Asynkron I/O verkar fortfarande vara synkron
Om du följde anvisningarna tidigare i den här artikeln är dock alla dina I/O-åtgärder fortfarande vanligtvis slutförda synkront i den utfärdade ordningen, och ingen av ReadFile
åtgärderna returnerar FALSE med GetLastError()
returnerande ERROR_IO_PENDING
, vilket innebär att du inte har tid för något bakgrundsarbete. Varför inträffar detta?
Det finns ett antal orsaker till att I/O-åtgärder slutförs synkront även om du har kodat för asynkron åtgärd.
Komprimering
Ett hinder för asynkron åtgärd är NTFS-komprimering (New Technology File System). Filsystemdrivrutinen kommer inte åt komprimerade filer asynkront. i stället görs alla åtgärder synkrona. Det här hindret gäller inte för filer som komprimeras med verktyg som liknar COMPRESS eller PKZIP.
NTFS-kryptering
På liknande sätt som komprimering gör filkryptering att systemdrivrutinen konverterar asynkron I/O till synkron. Om filerna dekrypteras blir I/O-begäranden asynkrona.
Utöka en fil
En annan orsak till att I/O-åtgärder slutförs synkront är själva åtgärderna. I Windows blir alla skrivåtgärder till en fil som utökar dess längd synkrona.
Kommentar
Program kan göra den tidigare nämnda skrivåtgärden asynkron genom att ändra filens giltiga datalängd med hjälp SetFileValidData
av funktionen och sedan utfärda en WriteFile
.
Med hjälp av SetFileValidData
(som är tillgängligt i Windows XP och senare versioner) kan program effektivt utöka filer utan att medföra prestandapåföljder för nollfyllning av dem.
Eftersom NTFS-filsystemet inte nollfyller data upp till den giltiga datalängden (VDL) som definieras av SetFileValidData
, har den här funktionen säkerhetskonsekvenser där filen kan tilldelas kluster som tidigare upptogs av andra filer. SetFileValidData
Kräver därför att anroparen har den nya SeManageVolumePrivilege
aktiverade (som standard tilldelas detta endast till administratörer). Microsoft rekommenderar att oberoende programvaruleverantörer (ISV: er) noggrant överväger konsekvenserna av att använda en sådan funktion.
Cache
De flesta I/O-drivrutiner (disk, kommunikation och andra) har specialfallskod där åtgärden slutförs om en I/O-begäran kan slutföras omedelbart och ReadFile
funktionen eller WriteFile
returnerar TRUE. På alla sätt verkar dessa typer av åtgärder vara synkrona. För en diskenhet kan vanligtvis en I/O-begäran slutföras omedelbart när data cachelagras i minnet.
Data finns inte i cacheminnet
Cacheschemat kan dock fungera mot dig om data inte finns i cacheminnet. Windows-cachen implementeras internt med hjälp av filmappningar. Minneshanteraren i Windows tillhandahåller ingen asynkron mekanism för sidfel för att hantera de filmappningar som används av cachehanteraren. Cachehanteraren kan kontrollera om den begärda sidan finns i minnet, så om du utfärdar en asynkron cachelagrad läsning och sidorna inte finns i minnet förutsätter filsystemdrivrutinen att du inte vill att tråden ska blockeras och begäran hanteras av en begränsad pool med arbetstrådar. Kontrollen returneras till programmet efter ReadFile
anropet med läsningen fortfarande väntande.
Detta fungerar bra för ett litet antal begäranden, men eftersom poolen med arbetstrådar är begränsad (för närvarande tre på ett 16 MB-system) finns det fortfarande bara ett fåtal begäranden i kö till diskdrivrutinen vid en viss tidpunkt. Om du utfärdar många I/O-åtgärder för data som inte finns i cacheminnet blir cachehanteraren och minneshanteraren mättade och dina begäranden görs synkrona.
Beteendet för cachehanteraren kan också påverkas baserat på om du kommer åt en fil sekventiellt eller slumpmässigt. Fördelarna med cachen visas mest vid åtkomst till filer sekventiellt. Flaggan FILE_FLAG_SEQUENTIAL_SCAN
i anropet CreateFile
optimerar cachen för den här typen av åtkomst. Men om du kommer åt filer på ett slumpmässigt sätt använder du FILE_FLAG_RANDOM_ACCESS
flaggan i CreateFile
för att instruera cachehanteraren att optimera dess beteende för slumpmässig åtkomst.
Använd inte cacheminnet
Flaggan FILE_FLAG_NO_BUFFERING
har störst effekt på filsystemets beteende för asynkron åtgärd. Det är det bästa sättet att garantera att I/O-begäranden är asynkrona. Det instruerar filsystemet att inte använda någon cachemekanism alls.
Kommentar
Det finns vissa begränsningar för att använda den här flaggan som har att göra med databuffertjusteringen och enhetens sektorstorlek. Mer information finns i funktionsreferensen i dokumentationen för funktionen CreateFile om hur du använder den här flaggan korrekt.
Verkliga testresultat
Följande är några testresultat från exempelkoden. Storleken på talen är inte viktig här och varierar från dator till dator, men förhållandet mellan talen jämfört med varandra belyser flaggornas allmänna effekt på prestandan.
Du kan förvänta dig att se resultat som liknar något av följande:
Test 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.
Det här testet visar att det tidigare nämnda programmet utfärdade 500 I/O-begäranden snabbt och hade mycket tid att utföra annat arbete eller utfärda fler begäranden.
Test 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.
Det här testet visar att det här programmet tillbringade 4,495880 sekunder med att anropa ReadFile för att slutföra sina åtgärder, men test 1 spenderade bara 0,224264 sekunder på att utfärda samma begäranden. I test 2 fanns det ingen extra tid för programmet att utföra något bakgrundsarbete.
Test 3
Asynchronous, buffered I/O: asynchio /f*.dat Operations completed in the order issued. 500 requests issued and completed in 0.251670 second.
Det här testet visar cachens synkrona karaktär. Alla läsningar utfärdades och slutfördes på 0,251670 sekund. Med andra ord slutfördes asynkrona begäranden synkront. Det här testet visar också cachehanterarens höga prestanda när data finns i cacheminnet.
Test 4
Synchronous, buffered I/O: asynchio /f*.dat /s Operations completed in the order issued. 500 requests and completed in 0.217011 seconds.
Det här testet visar samma resultat som i test 3. Synkrona läsningar från cacheminnet slutförs lite snabbare än asynkrona läsningar från cacheminnet. Det här testet visar också cachehanterarens höga prestanda när data finns i cacheminnet.
Slutsats
Du kan bestämma vilken metod som är bäst eftersom allt beror på typ, storlek och antal åtgärder som programmet utför.
Standardfilens åtkomst utan att ange några särskilda flaggor till CreateFile
är en synkron och cachelagrad åtgärd.
Kommentar
Du får ett visst automatiskt asynkront beteende i det här läget eftersom filsystemdrivrutinen utför prediktiv asynkron läsning och asynkron lat skrivning av ändrade data. Även om det här beteendet inte gör programmets I/O asynkront, är det idealiskt för de allra flesta enkla program.
Om programmet däremot inte är enkelt kan du behöva utföra profilering och prestandaövervakning för att fastställa den bästa metoden, ungefär som de tester som illustreras tidigare i den här artikeln. Det är användbart att profilera den tid som spenderas i ReadFile
funktionen eller WriteFile
och sedan jämföra den här tiden med hur lång tid det tar för faktiska I/O-åtgärder att slutföras. Om merparten av tiden ägnas åt att faktiskt utfärda I/O slutförs din I/O synkront. Men om den tid som ägnas åt att utfärda I/O-begäranden är relativt liten jämfört med den tid det tar för I/O-åtgärderna att slutföras, behandlas dina åtgärder asynkront. Exempelkoden som nämns tidigare i den QueryPerformanceCounter
här artikeln använder funktionen för att göra sin egen interna profilering.
Prestandaövervakning kan hjälpa dig att avgöra hur effektivt programmet använder disken och cacheminnet. Om du spårar någon av prestandaräknarna för cacheobjektet anger du cachehanterarens prestanda. Om du spårar prestandaräknarna för fysiska diskar eller logiska diskobjekt visas disksystemens prestanda.
Det finns flera verktyg som är användbara vid prestandaövervakning. PerfMon
och DiskPerf
är särskilt användbara. För att systemet ska kunna samla in data om disksystemens prestanda måste du först utfärda DiskPerf
kommandot. När du har kört kommandot måste du starta om systemet för att starta datainsamlingen.