E/A-Abschlussports

E/A-Abschlussports bieten ein effizientes Threadingmodell für die Verarbeitung mehrerer asynchroner E/A-Anforderungen auf einem Multiprozessorsystem. Wenn ein Prozess einen E/A-Abschlussport erstellt, erstellt das System ein zugeordnetes Warteschlangenobjekt für Anforderungen, deren einziger Zweck darin besteht, diese Anforderungen zu verarbeiten. Prozesse, die viele gleichzeitige asynchrone E/A-Anforderungen verarbeiten, können dies schneller und effizienter tun, indem E/A-Abschlussports in Verbindung mit einem vorab zugeordneten Threadpool verwendet werden, als durch das Erstellen von Threads zum Zeitpunkt des Empfangs einer E/A-Anforderung.

Funktionsweise von E/A-Abschlussports

Die CreateIoCompletionPort-Funktion erstellt einen E/A-Abschlussport und ordnet diesem Port ein oder mehrere Dateihandles zu. Wenn ein asynchroner E/A-Vorgang für eine dieser Dateihandles abgeschlossen wird, wird ein E/A-Abschlusspaket in fifo-Reihenfolge (First-in-First-Out) in die Warteschlange des zugeordneten E/A-Abschlussports eingereiht. Eine leistungsstarke Verwendung dieses Mechanismus besteht darin, den Synchronisierungspunkt für mehrere Dateihandles in einem einzelnen Objekt zu kombinieren, obwohl es auch andere nützliche Anwendungen gibt. Beachten Sie, dass die Pakete zwar in der FIFO-Reihenfolge in die Warteschlange eingereiht werden, aber möglicherweise in einer anderen Reihenfolge aus der Warteschlange genommen werden.

Hinweis

Der hier verwendete Begriff Dateihandle bezieht sich auf eine Systemabstraktion, die einen überlappenden E/A-Endpunkt darstellt, nicht nur eine Datei auf dem Datenträger. Dies kann beispielsweise ein Netzwerkendpunkt, ein TCP-Socket, eine Named Pipe oder ein E-Mail-Slot sein. Jedes Systemobjekt, das überlappende E/A unterstützt, kann verwendet werden. Eine Liste der zugehörigen E/A-Funktionen finden Sie am Ende dieses Themas.

Wenn ein Dateihandle einem Abschlussport zugeordnet ist, wird der übergebene Statusblock erst aktualisiert, wenn das Paket aus dem Abschlussport entfernt wird. Die einzige Ausnahme ist, wenn der ursprüngliche Vorgang synchron mit einem Fehler zurückgegeben wird. Ein Thread (entweder vom Hauptthread oder vom Hauptthread selbst erstellt) verwendet die GetQueuedCompletionStatus-Funktion, um zu warten, bis ein Abschlusspaket in die Warteschlange des E/A-Abschlussports eingereiht wird, anstatt direkt auf den Abschluss der asynchronen E/A zu warten. Threads, die ihre Ausführung an einem E/A-Abschlussport blockieren, werden in LIFO-Reihenfolge (Last In First Out) freigegeben, und das nächste Abschlusspaket wird aus der FIFO-Warteschlange des E/A-Abschlussports für diesen Thread abgerufen. Dies bedeutet, dass beim Loslassen eines Vervollständigungspakets an einen Thread das System den letzten (letzten) Thread freigibt, der diesem Port zugeordnet ist, und ihm die Abschlussinformationen für den ältesten E/A-Abschluss übergeben.

Obwohl eine beliebige Anzahl von Threads GetQueuedCompletionStatus für einen angegebenen E/A-Abschlussport aufrufen kann, wird sie dem angegebenen E/A-Abschlussport zugeordnet, wenn ein angegebener Thread GetQueuedCompletionStatus zum ersten Mal aufruft, bis einer von drei Dingen auftritt: Der Thread wird beendet, gibt einen anderen E/A-Abschlussport an oder schließt den E/A-Abschlussport. Anders ausgedrückt: Ein einzelner Thread kann höchstens einem E/A-Abschlussport zugeordnet werden.

Wenn ein Vervollständigungspaket an einem E/A-Abschlussport in die Warteschlange eingereiht wird, überprüft das System zunächst, wie viele Threads, die diesem Port zugeordnet sind, ausgeführt werden. Wenn die Anzahl der ausgeführten Threads kleiner ist als der Parallelitätswert (wird im nächsten Abschnitt erläutert), kann einer der wartenden Threads (der letzte) das Abschlusspaket verarbeiten. Wenn ein ausgeführter Thread seine Verarbeitung abschließt, ruft er in der Regel GetQueuedCompletionStatus erneut auf. An diesem Punkt wird entweder mit dem nächsten Abschlusspaket zurückgegeben, oder es wird gewartet, wenn die Warteschlange leer ist.

Threads können die PostQueuedCompletionStatus-Funktion verwenden, um Vervollständigungspakete in der Warteschlange eines E/A-Abschlussports zu platzieren. Auf diese Weise kann der Abschlussport verwendet werden, um Kommunikation von anderen Threads des Prozesses zu empfangen, zusätzlich zum Empfangen von E/A-Abschlusspaketen vom E/A-System. Mit der PostQueuedCompletionStatus-Funktion kann eine Anwendung ihre eigenen speziellen Vervollständigungspakete an den E/A-Abschlussport in die Warteschlange stellen, ohne einen asynchronen E/A-Vorgang zu starten. Dies ist beispielsweise nützlich, um Arbeitsthreads über externe Ereignisse zu benachrichtigen.

Das E/A-Abschlussporthandle und jedes Dateihandle, das diesem bestimmten E/A-Abschlussport zugeordnet ist, werden als Verweise auf den E/A-Abschlussport bezeichnet. Der E/A-Abschlussport wird freigegeben, wenn keine Verweise mehr darauf vorhanden sind. Daher müssen alle diese Handles ordnungsgemäß geschlossen werden, um den E/A-Abschlussport und die zugehörigen Systemressourcen freizugeben. Nachdem diese Bedingungen erfüllt sind, sollte eine Anwendung das E/A-Abschlussporthandle schließen, indem sie die CloseHandle-Funktion aufruft.

Hinweis

Ein E/A-Abschlussport ist dem Prozess zugeordnet, der ihn erstellt hat, und kann nicht zwischen Prozessen aufgeteilt werden. Ein einzelnes Handle kann jedoch zwischen Threads im gleichen Prozess aufgeteilt werden.

Threads und Parallelität

Die wichtigste Eigenschaft eines E/A-Abschlussports, die sorgfältig zu berücksichtigen ist, ist der Parallelitätswert. Der Parallelitätswert eines Abschlussports wird angegeben, wenn er mit CreateIoCompletionPort über den NumberOfConcurrentThreads-Parameter erstellt wird. Dieser Wert schränkt die Anzahl der ausführungsfähigen Threads ein, die dem Abschlussport zugeordnet sind. Wenn die Gesamtzahl der dem Abschlussport zugeordneten auslaufbaren Threads den Parallelitätswert erreicht, blockiert das System die Ausführung aller nachfolgenden Threads, die diesem Abschlussport zugeordnet sind, bis die Anzahl der auslaufbaren Threads unter den Parallelitätswert fällt.

Das effizienteste Szenario tritt auf, wenn Abschlusspakete in der Warteschlange warten, aber keine Wartezeiten erfüllt werden können, da der Port sein Parallelitätslimit erreicht hat. Überlegen Sie, was mit einem Parallelitätswert von einem und mehreren Threads geschieht, die im GetQueuedCompletionStatus-Funktionsaufruf warten. Wenn in diesem Fall in der Warteschlange immer Abschlusspakete warten, wenn der ausgeführte Thread GetQueuedCompletionStatus aufruft, wird die Ausführung nicht blockiert, da die Threadwarteschlange, wie bereits erwähnt, LIFO ist. Stattdessen übernimmt dieser Thread sofort das nächste Abschlusspaket in der Warteschlange. Es treten keine Threadkontextwechsel auf, da der ausgeführte Thread kontinuierlich Vervollständigungspakete aufnimmt und die anderen Threads nicht ausgeführt werden können.

Hinweis

Im vorherigen Beispiel scheinen die zusätzlichen Threads unbrauchbar zu sein und nie ausgeführt zu werden. Dabei wird jedoch davon ausgegangen, dass der ausgeführte Thread nie durch einen anderen Mechanismus in einen Wartezustand versetzt wird, den zugehörigen E/A-Abschlussport beendet oder anderweitig schließt. Berücksichtigen Sie alle diese Threadausführungsausweitungen beim Entwerfen der Anwendung.

Der beste maximale Gesamtwert für den Parallelitätswert ist die Anzahl der CPUs auf dem Computer. Wenn ihre Transaktion eine lange Berechnung erfordert, ermöglicht ein größerer Parallelitätswert die Ausführung weiterer Threads. Die Fertigstellung jedes Abschlusspakets kann länger dauern, aber es werden mehr Vervollständigungspakete gleichzeitig verarbeitet. Sie können mit dem Parallelitätswert in Verbindung mit Profilerstellungstools experimentieren, um die beste Auswirkung für Ihre Anwendung zu erzielen.

Das System ermöglicht es auch einem Thread, der in GetQueuedCompletionStatus wartet, ein Vervollständigungspaket zu verarbeiten, wenn ein anderer ausgeführter Thread, der dem gleichen E/A-Abschlussport zugeordnet ist, aus anderen Gründen in einen Wartezustand wechselt, z. B. die SuspendThread-Funktion. Wenn der Thread im Wartezustand wieder ausgeführt wird, kann es einen kurzen Zeitraum geben, in dem die Anzahl der aktiven Threads den Parallelitätswert überschreitet. Das System reduziert diese Anzahl jedoch schnell, indem es keine neuen aktiven Threads zulässt, bis die Anzahl der aktiven Threads unter den Parallelitätswert fällt. Dies ist ein Grund dafür, dass Ihre Anwendung mehr Threads in ihrem Threadpool erstellt als der Parallelitätswert. Die Threadpoolverwaltung geht über den Rahmen dieses Themas hinaus, aber eine gute Faustregel besteht darin, mindestens doppelt so viele Threads im Threadpool zu haben, wie Prozessoren auf dem System vorhanden sind. Weitere Informationen zum Threadpooling finden Sie unter Threadpools.

Unterstützte E/A-Funktionen

Die folgenden Funktionen können verwendet werden, um E/A-Vorgänge zu starten, die mithilfe von E/A-Abschlussports abgeschlossen werden. Sie müssen der Funktion eine Instanz der OVERLAPPED-Struktur und ein Dateihandle übergeben, das zuvor einem E/A-Abschlussport zugeordnet war (durch einen Aufruf von CreateIoCompletionPort),um den E/A-Abschlussportmechanismus zu aktivieren:

About Processes and Threads (Informationen zu Prozessen und Threads)

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus