E/A-Abschlussports

E/A-Vervollständigungsports 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 Threads, deren einziger Zweck die Verarbeitung dieser Anforderungen ist. Prozesse, die viele gleichzeitige asynchrone E/A-Anforderungen verarbeiten, können dies schneller und effizienter erreichen, indem E/A-Abschlussports in Verbindung mit einem vorab zugewiesenen Threadpool verwendet werden, als threads zum Zeitpunkt des Empfangens einer E/A-Anforderung zu erstellen.

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 ist, wird ein E/A-Vervollständigungspaket in der FIFO-Reihenfolge (First-in-First-Out) an den zugeordneten E/A-Abschlussport in die Warteschlange gestellt. Eine leistungsstarke Verwendung für diesen 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 in einer anderen Reihenfolge aus der Warteschlange entfernt werden können.

Hinweis

Der Begriff Dateihandle , wie hier verwendet, bezieht sich auf eine Systemstraktion, die einen überlappenden E/A-Endpunkt darstellt, nicht nur eine Datei auf dem Datenträger. Beispielsweise kann es sich um einen Netzwerkendpunkt, einen TCP-Socket, eine Named Pipe oder einen E-Mail-Slot handeln. Es kann jedes Systemobjekt verwendet werden, das überlappende E/A unterstützt. Eine Liste der zugehörigen E/A-Funktionen finden Sie am Ende dieses Themas.

 

Wenn ein Dateihandle einem Abschlussport zugeordnet ist, wird der übergebene status Block erst aktualisiert, wenn das Paket vom Abschlussport entfernt wurde. Die einzige Ausnahme ist, wenn der ursprüngliche Vorgang synchron mit einem Fehler zurückgibt. Ein Thread (entweder vom Standard Thread oder vom Standard Thread selbst erstellt) verwendet die GetQueuedCompletionStatus-Funktion, um auf die Warteschlange eines Abschlusspakets am E/A-Abschlussport zu warten, anstatt direkt auf den Abschluss der asynchronen E/A zu warten. Threads, die ihre Ausführung an einem E/A-Abschlussport blockieren, werden in der 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 das System, wenn ein Vervollständigungspaket für einen Thread freigegeben wird, den letzten (aktuellsten) Thread freigibt, der diesem Port zugeordnet ist, und die Abschlussinformationen für den ältesten E/A-Abschluss übergibt.

Obwohl eine beliebige Anzahl von Threads GetQueuedCompletionStatus für einen angegebenen E/A-Abschlussport aufrufen kann, wird er dem angegebenen E/A-Abschlussport zugeordnet, wenn ein angegebener Thread GetQueuedCompletionStatus zum ersten Mal aufruft, wird er dem angegebenen E/A-Abschlussport zugeordnet, bis eine von drei Dingen eintritt: 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 Abschlusspaket 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 als der Parallelitätswert ist (im nächsten Abschnitt erläutert), kann einer der wartenden Threads (der letzte) das Abschlusspaket verarbeiten. Wenn ein ausgeführter Thread seine Verarbeitung abgeschlossen hat, ruft er in der Regel GetQueuedCompletionStatus erneut auf. An diesem Punkt wird entweder mit dem nächsten Abschlusspaket zurückgegeben oder gewartet, wenn die Warteschlange leer ist.

Threads können die PostQueuedCompletionStatus-Funktion verwenden, um Abschlusspakete in der Warteschlange eines E/A-Vervollständigungsports zu platzieren. Auf diese Weise kann der Abschlussport verwendet werden, um Kommunikationen von anderen Threads des Prozesses zu empfangen, zusätzlich zum Empfangen von E/A-Abschlusspaketen vom E/A-System. Die PostQueuedCompletionStatus-Funktion ermöglicht es einer Anwendung, ihre eigenen speziellen Abschlusspakete am E/A-Abschlussport in die Warteschlange zu stellen, ohne einen asynchronen E/A-Vorgang zu starten. Dies ist z. B. nützlich, um Workerthreads über externe Ereignisse zu benachrichtigen.

Das E/A-Vervollständigungsporthandle 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-Vervollständigungshandle-Porthandle-Handle durch Aufrufen der CloseHandle-Funktion schließen.

Hinweis

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

 

Threads und Parallelität

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

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

Hinweis

Im vorherigen Beispiel scheinen die zusätzlichen Threads nutzlos zu sein und werden nie ausgeführt, aber das setzt voraus, dass der ausgeführte Thread niemals durch einen anderen Mechanismus in einen Wartezustand versetzt wird, beendet oder auf andere Weise den zugeordneten E/A-Abschlussport schließt. Berücksichtigen Sie beim Entwerfen der Anwendung alle diese Threadausführungsverzweigungen.

 

Der beste maximal zu wählende Gesamtwert für den Parallelitätswert ist die Anzahl der CPUs auf dem Computer. Wenn ihre Transaktion eine lange Berechnung erforderte, ermöglicht ein größerer Parallelitätswert die Ausführung weiterer Threads. Jedes Vervollständigungspaket kann länger dauern, aber gleichzeitig werden mehr Abschlusspakete verarbeitet. Sie können mit dem Parallelitätswert in Verbindung mit Profilerstellungstools experimentieren, um die beste Wirkung für Ihre Anwendung zu erzielen.

Das System lässt auch zu, dass ein Thread, der in GetQueuedCompletionStatus wartet, ein Abschlusspaket verarbeitet, wenn ein anderer ausgeführter Thread, der demselben 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 im 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-Vervollständigungsports abgeschlossen werden. Sie müssen der Funktion eine instance der OVERLAPPED-Struktur und ein Dateihandle übergeben, das zuvor einem E/A-Abschlussport (durch einen Aufruf von CreateIoCompletionPort) zugeordnet ist, um den E/A-Abschlussportmechanismus zu aktivieren:

About Processes and Threads (Informationen zu Prozessen und Threads)

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus