I/O-slutförandeportar

I/O-slutförandeportar ger en effektiv trådningsmodell för bearbetning av flera asynkrona I/O-begäranden i ett system med flera processorer. När en process skapar en I/O-slutförandeport skapar systemet ett associerat köobjekt för trådar vars enda syfte är att betjäna dessa begäranden. Processer som hanterar många samtidiga asynkrona I/O-begäranden kan göra det snabbare och effektivare genom att använda I/O-slutförandeportar tillsammans med en förallokerad trådpool än genom att skapa trådar när de tar emot en I/O-begäran.

När du ska använda I/O-slutförandeportar

Scenario Rekommenderat tillvägagångssätt
Högpresterande server som hanterar hundratals/tusentals samtidiga anslutningar I/O-kompletteringsportar – särskilt utformade för detta. Kerneln hanterar trådschemaläggning för att matcha CPU-samtidighet.
Måttlig samtidighet (tiotals asynkrona åtgärder) Trådpoolens I/O (CreateThreadpoolIo) – enklare API, hanterar IOCP internt. Detta bör föredras för ny kod som inte behöver manuell kontroll av trådar.
Enkla asynkrona filåtgärder i modern C++ C++20-coroutines med en anpassad IOCP-dispatcher, eller .NET FileStream med async/await.
Enkeltrådad eller låg volym-I/O Synkron I/O eller enkel överlappande I/O med händelsesignalering. IOCP lägger till onödig komplexitet för scenarier med enkel ström.

Obs!

API för trådpool jämfört med rå IOCP: API:et för Windows trådpool (CreateThreadpoolIo, StartThreadpoolIo) använder IOCP internt men hanterar trådens livscykelhantering automatiskt. För nya serverprogram bör du överväga trådpools-API:et först – det ger samma skalbarhet med mindre pannplåtskod. Använd rå IOCP när du behöver explicit kontroll över slutförandeportens samtidighetsvärde eller anpassad trådhantering.

Så här fungerar I/O-slutförandeportar

Funktionen CreateIoCompletionPort skapar en I/O-slutförandeport och associerar en eller flera filreferenser med den porten. När en asynkron I/O-operation på ett av dessa filhandtag slutförs köas ett slutförandepaket för I/O i först in, först ut-ordning (FIFO) till den associerade I/O-slutförandeporten. En kraftfull användning för den här mekanismen är att kombinera synkroniseringspunkten för flera filhandtag till ett enda objekt, även om det också finns andra användbara program. Observera att även om paketen läggs i kö i FIFO-ordning kan de tas ut ur kön i en annan ordning.

Obs!

Termen filreferens som används här refererar till en systemabstraktion som representerar en överlappande I/O-slutpunkt, inte bara en fil på disken. Det kan till exempel vara en nätverksslutpunkt, TCP-socket, med namnet pipe eller e-postfack. Alla systemobjekt som stöder överlappande I/O kan användas. En lista över relaterade I/O-funktioner finns i slutet av det här avsnittet.

När en filreferens är associerad med en slutförandeport uppdateras inte statusblocket som skickas in förrän paketet tas bort från slutförandeporten. Det enda undantaget är om den ursprungliga operationen returnerar synkront med ett fel. En tråd (antingen en som skapats av huvudtråden eller själva huvudtråden) använder funktionen GetQueuedCompletionStatus för att vänta tills ett slutförandepaket har placerats i kö till I/O-slutförandeporten i stället för att vänta direkt på att den asynkrona I/O ska slutföras. Trådar som blockerar körningen på en I/O-slutförandeport släpps i LIFO-ordning (last-in-first-out) och nästa slutförandepaket hämtas från I/O-slutförandeportens FIFO-kö för den tråden. Det innebär att när ett slutförandepaket ges till en tråd frigör systemet den senaste tråden som är associerad med porten och skickar information om slutförandet för det äldsta I/O-slutförandet till den.

Även om valfritt antal trådar kan anropa GetQueuedCompletionStatus för en angiven I/O-slutförandeport, när en angiven tråd anropar GetQueuedCompletionStatus första gången, associeras den med den angivna I/O-slutförandeporten tills någon av tre saker inträffar: Tråden avslutas, anger en annan I/O-slutförandeport eller stänger I/O-slutförandeporten. Med andra ord kan en enda tråd associeras med högst en I/O-slutförandeport.

När ett slutförandepaket placeras i kö till en I/O-slutförandeport kontrollerar systemet först hur många trådar som är associerade med den porten. Om antalet trådar som körs är mindre än samtidighetsvärdet (beskrivs i nästa avsnitt) tillåts en av de väntande trådarna (den senaste) bearbeta slutförandepaketet. När en tråd som körs slutför bearbetningen anropas vanligtvis GetQueuedCompletionStatus igen. Då returneras den antingen med nästa slutförandepaket eller väntar om kön är tom.

Trådar kan använda funktionen PostQueuedCompletionStatus för att placera slutförandepaket i en I/O-slutförandeports kö. På så sätt kan slutförandeporten användas för att ta emot kommunikation från andra trådar i processen, förutom att ta emot I/O-slutförandepaket från I/O-systemet. Med funktionen PostQueuedCompletionStatus kan ett program köa sina egna paket för specialändamål till I/O-slutförandeporten utan att starta en asynkron I/O-åtgärd. Detta är användbart för att till exempel meddela arbetstrådar om externa händelser.

Handtaget till I/O-slutförandeporten och varje filhandtag som är associerat med just den I/O-slutförandeporten kallas referenser till I/O-slutförandeporten. I/O-slutförandeporten släpps när det inte finns några fler referenser till den. Därför måste alla dessa handtag stängas korrekt för att frigöra I/O-slutförandeporten och dess tillhörande systemresurser. När dessa villkor är uppfyllda bör ett program stänga I/O-slutförandeporthandtaget genom att anropa funktionen CloseHandle .

Obs!

En I/O-slutförandeport är associerad med processen som skapade den och kan inte delas mellan processer. Ett enda handtag kan dock delas mellan trådar i samma process.

Trådar och samtidighet

Den viktigaste egenskapen för en I/O-slutförandeport att överväga noggrant är samtidighetsvärdet. Samtidighetsvärdet för en slutförandeport anges när den skapas med CreateIoCompletionPort via parametern NumberOfConcurrentThreads . Det här värdet begränsar antalet körbara trådar som är kopplade till slutförandeporten. När det totala antalet runnable-trådar som är associerade med slutförandeporten når samtidighetsvärdet blockerar systemet körningen av efterföljande trådar som är associerade med den slutförandeporten tills antalet runnable trådar sjunker under samtidighetsvärdet.

Det mest effektiva scenariot inträffar när det finns slutförandepaket som väntar i kön, men inga väntetider kan uppfyllas eftersom porten har nått sin samtidighetsgräns. Tänk på vad som händer med ett samtidighetsvärde för en och flera trådar som väntar i funktionsanropet GetQueuedCompletionStatus . I det här fallet, om kön alltid har slutförandepaket som väntar, kommer exekveringen inte att blockeras när den tråd som körs anropar GetQueuedCompletionStatus, eftersom trådkön, som tidigare nämnts, är LIFO. I stället hämtar den här tråden omedelbart nästa slutförandepaket i kön. Inga trådkontextväxlar inträffar eftersom den tråd som körs kontinuerligt plockar upp kompletteringspaket och de andra trådarna inte kan köras.

Obs!

I föregående exempel verkar de extra trådarna vara värdelösa och aldrig köras, men det förutsätter att den löpande tråden aldrig hamnar i väntetillstånd av någon annan mekanism, avslutar eller på annat sätt stänger sin associerade I/O-slutförandeport. Tänk på alla sådana trådkörningsförgreningar när du utformar programmet.

Det bästa övergripande maximala värdet som ska väljas för samtidighetsvärdet är antalet processorer på datorn. Om transaktionen kräver en lång beräkning tillåter ett större samtidighetsvärde fler trådar att köras. Varje slutförandepaket kan ta längre tid att slutföra, men fler slutförandepaket bearbetas samtidigt. Du kan experimentera med samtidighetsvärdet tillsammans med profileringsverktyg för att uppnå bästa möjliga effekt för ditt program.

Systemet tillåter också att en tråd som väntar i GetQueuedCompletionStatus bearbetar ett slutförandepaket om en annan tråd som körs som är associerad med samma I/O-slutförandeport anger väntetillstånd av andra skäl, till exempel funktionen SuspendThread . När tråden i väntetillståndet börjar köras igen kan det finnas en kort period när antalet aktiva trådar överskrider samtidighetsvärdet. Systemet minskar dock snabbt det här antalet genom att inte tillåta några nya aktiva trådar förrän antalet aktiva trådar hamnar under samtidighetsvärdet. Det här är en anledning till att programmet skapar fler trådar i sin trådpool än samtidighetsvärdet. Hantering av trådpooler ligger utanför omfånget för det här ämnet, men en bra tumregel är att ha minst dubbelt så många trådar i trådpoolen som det finns processorer i systemet. Mer information om trådpooler finns i trådpooler.

I/O-funktioner som stöds

Följande funktioner kan användas för att starta I/O-åtgärder som slutförs med hjälp av I/O-slutförandeportar. Du måste skicka funktionen till en instans av OVERLAPPED-strukturen och ett filhandtag som tidigare associerats med en I/O-slutförandeport (genom ett anrop till CreateIoCompletionPort) för att aktivera I/O-slutförandeportmekanismen:

Om processer och trådar

BindIoCompletionCallback

CreateIoCompletionPort