Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
E/A-Abschlussports bieten ein effizientes Threading-Modell für die Verarbeitung mehrerer asynchroner E/A-Anforderungen in einem Multiprozessorsystem. Wenn ein Prozess einen E/A-Vervollständigungsport erstellt, erstellt das System ein zugeordnetes Warteschlangenobjekt für Threads, 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-Vervollständigungsports in Verbindung mit einem vorab zugewiesenen Threadpool verwendet werden, als zum Zeitpunkt der Übermittlung einer E/A-Anforderung Threads zu erstellen.
Wann E/A-Abschlussports verwendet werden sollten
| Szenario | Empfohlener Ansatz |
|---|---|
| Hochleistungsserver, der Hunderte/Tausende gleichzeitiger Verbindungen verarbeitet | I/O Completion Ports – speziell dafür entwickelt. Der Kernel verwaltet die Threadplanung so, dass sie der CPU-Parallelität entspricht. |
| Moderate Parallelität (zehn asynchrone Vorgänge) |
Threadpool-E/A (CreateThreadpoolIo) – einfachere API, verwaltet das IOCP intern. Bevorzugen Sie neuen Code, der keine manuelle Threadsteuerung benötigt. |
| Einfache asynchrone Dateivorgänge in modernen C++ | C++20 Co-Routinen mit einem benutzerdefinierten IOCP-Verteiler oder .NET FileStream mit async/await. |
| Single-Thread-E/A oder E/A mit geringem Volumen | Synchrone E/A oder einfache überlappende E/A mit Ereignissignal. IOCP fügt unnötige Komplexität für Szenarien mit einem einzigen Datenstrom hinzu. |
Anmerkung
Threadpool-API im Vergleich zu unformatiertem IOCP: Die Windows Threadpool-API (CreateThreadpoolIo, StartThreadpoolIo) verwendet IOCP intern, behandelt jedoch die Threadlebenszyklusverwaltung automatisch. Berücksichtigen Sie bei neuen Serveranwendungen zuerst die Threadpool-API – sie bietet die gleiche Skalierbarkeit mit weniger Codebausteinen. Verwenden Sie unformatierte IOCP, wenn Sie explizite Kontrolle über den Parallelitätswert des Abschlussports oder die benutzerdefinierte Threadverwaltung benötigen.
Funktionsweise von E/A-Abschlussports
Die CreateIoCompletionPort-Funktion erstellt einen E/A-Vervollständigungsport und ordnet einen oder mehrere Dateihandles diesem Port zu. Wenn ein asynchroner E/A-Vorgang auf einem dieser Dateihandles abgeschlossen ist, wird ein E/A-Abschlusspaket in die Warteschlange in der FIFO-Reihenfolge (First-in-First-Out) an den zugeordneten E/A-Abschlussport 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. Bitte beachten Sie, dass die Pakete zwar nach dem FIFO-Prinzip in die Warteschlange eingereiht werden, jedoch möglicherweise in einer anderen Reihenfolge aus der Warteschlange entfernt werden.
Anmerkung
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, einen benannten Pipe- oder einen E-Mail-Steckplatz handeln. Jedes Systemobjekt, das überlappende E/A unterstützt, kann verwendet werden. Eine Liste verwandter 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 besteht, wenn die ursprüngliche Operation synchron einen Fehler zurückgibt. Ein Thread (entweder ein Thread, der vom Hauptthread oder dem Hauptthread selbst erstellt wurde) verwendet den GetQueuedCompletionStatus--Funktion, um zu warten, dass ein Abschlusspaket an den E/A-Abschlussport in die Warteschlange gestellt wird, anstatt direkt auf den Abschluss der asynchronen E/A zu warten. Threads, die ihre Ausführung auf einem Abschlussport blockieren, werden in der LIFO-Reihenfolge (Last-In-First-Out) freigegeben, und das nächste Abschlusspaket wird für den betreffenden Thread aus der FIFO-Warteschlange des E/A-Abschlussports abgerufen. Dies bedeutet, dass das System bei Freigabe eines Abschlusspakets an einen Thread den diesem Port zuletzt zugeordneten Thread aktiviert und ihm die Abschlussinformationen für den ältesten E/A-Abschluss übergibt.
Obwohl eine beliebige Anzahl von Threads GetQueuedCompletionStatus für einen angegebenen E/A-Vervollständigungsport aufrufen kann, wenn ein angegebener Thread GetQueuedCompletionStatus zum ersten Mal aufruft, wird er dem angegebenen E/A-Abschlussport zugeordnet, bis eines von drei Dingen auftritt: Der Thread beendet, gibt einen anderen E/A-Vervollständigungsport an oder schließt den E/A-Vervollständigungsport. Mit anderen Worten, ein einzelner Thread kann höchstens einem E/A-Vervollständigungsport zugeordnet werden.
Wenn ein Vervollständigungspaket an einem E/A-Abschlussport in die Warteschlange gestellt wird, überprüft das System zunächst, wie viele Threads mit diesem Port verbunden sind. Wenn die Anzahl der aktiven Threads kleiner ist als der Grad der Parallelität (wie im nächsten Abschnitt erläutert), darf einer der wartenden Threads – und zwar der zuletzt hinzugekommene – das Abschlusspaket verarbeiten. Wenn ein laufender Thread die Verarbeitung abgeschlossen hat, ruft er in der Regel erneut GetQueuedCompletionStatus auf, woraufhin entweder das nächste Abschlusspaket empfangen wird oder er wartet, wenn die Warteschlange leer ist.
Threads können die Funktion PostQueuedCompletionStatus verwenden, um Abschlusspakete in die Warteschlange eines E/A-Abschlussports zu stellen. Dadurch kann der Abschlussport verwendet werden, um Kommunikationen von anderen Threads des Prozesses zu empfangen, zusätzlich zum Empfangen von E/A-Vervollständigungspaketen aus dem 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 hilfreich, um Arbeitsthreads über externe Ereignisse zu benachrichtigen.
Das Handle für den E/A-Abschlussport und jedes Dateihandle, das diesem speziellen 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 wurden, sollte eine Anwendung den E/A-Abschlussport-Handle durch Aufrufen der Funktion CloseHandle schließen.
Anmerkung
Ein E/A-Abschlussport ist dem Prozess zugeordnet, der ihn erstellt hat. Er kann nicht von mehreren Prozessen gemeinsam genutzt werden. Dasselbe Handle kann jedoch von Threads innerhalb desselben Prozesses gemeinsam genutzt werden.
Threads und Parallelität
Die wichtigste Eigenschaft eines E/A-Abschlussports, die sorgfältig bedacht werden sollte, ist der Parallelitätswert. Der Parallelitätswert eines Abschlussports wird festgelegt, wenn dieser mit CreateIoCompletionPort und dem Parameter NumberOfConcurrentThreads erstellt wird. Dieser Wert beschränkt die Anzahl der ausgeführten Threads, die dem Abschlussport zugeordnet sind. Wenn die Gesamtanzahl 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 Abschlusspakete in der Warteschlange warten, aber keine Wartezeiten erfüllt werden können, da der Port seine Parallelitätsgrenze erreicht hat. Berücksichtigen Sie, was passiert, wenn der Parallelitätswert eins beträgt und mehrere Threads im Aufruf der Funktion GetQueuedCompletionStatus warten. In diesem Fall wird die Ausführung nicht blockiert, wenn die Warteschlange stets ausstehende Abschlusspakete enthält und der laufende Thread GetQueuedCompletionStatus aufruft, da die Thread-Warteschlange, wie bereits erwähnt, nach dem LIFO-Prinzip funktioniert. Stattdessen greift dieser Thread sofort auf das nächste Abschlusspaket in der Warteschlange zu. Es treten keine Thread-Kontextwechsel auf, da der laufende Thread fortlaufend Abschlusspakete verarbeitet und die anderen Threads nicht zur Ausführung kommen können.
Anmerkung
Im vorherigen Beispiel scheinen die zusätzlichen Threads nutzlos zu sein und nie ausgeführt zu werden, aber es wird davon ausgegangen, dass der ausgeführte Thread nie von einem anderen Mechanismus in einen Wartezustand versetzt wird, beendet oder anderweitig den zugeordneten E/A-Abschlussport schließt. Berücksichtigen Sie beim Entwerfen der Anwendung alle derartigen Threadausführungs-Auswirkungen.
Der beste Gesamthöchstwert, der für den Parallelitätswert ausgewählt werden soll, ist die Anzahl der CPUs auf dem Computer. Wenn ihre Transaktion eine langwierige Berechnung erforderte, kann ein größerer Parallelitätswert mehr Threads ausführen. Jedes Vervollständigungspaket kann länger dauern, aber gleichzeitig werden mehr Vervollständigungspakete verarbeitet. Sie können mit dem Parallelitätswert in Verbindung mit Profilerstellungstools experimentieren, um den besten Effekt für Ihre Anwendung zu erzielen.
Das System ermöglicht auch, dass ein Thread, der in GetQueuedCompletionStatus wartet, ein Vervollständigungspaket verarbeiten kann, wenn ein anderer ausgeführter Thread, der demselben E/A-Vervollständigungsport zugeordnet ist, aus anderen Gründen einen Wartezustand eingibt, z. B. die SuspendThread-Funktion . Wenn der Thread im Wartezustand erneut ausgeführt wird, kann es einen kurzen Zeitraum geben, in dem die Anzahl der aktiven Threads den Parallelitätswert überschreitet. Das System reduziert diese Zahl jedoch schnell, indem keine neuen aktiven Threads zugelassen werden, bis die Anzahl der aktiven Threads unter den Parallelitätswert fällt. Dies ist ein Grund dafür, dass Ihre Anwendung mehr Threads in seinem Threadpool erstellt, als der Parallelitätswert. Threadpoolverwaltung geht über den Umfang dieses Themas hinaus, aber eine gute Faustregel besteht darin, mindestens doppelt so viele Threads im Threadpool zu haben, wie es Prozessoren im System gibt. Weitere Informationen zum Threadpooling finden Sie unter Threadpools.
Unterstützte I/O-Funktionen
Die folgenden Funktionen können verwendet werden, um E/A-Vorgänge zu starten, die unter Verwendung von E/A-Abschlussports abgeschlossen werden. Sie müssen die Funktion an eine Instanz der OVERLAPPED-Struktur und ein Dateihandle übergeben, das zuvor einem E/A-Vervollständigungsport zugeordnet war (durch einen Aufruf von CreateIoCompletionPort), um den E/A-Vervollständigungsportmechanismus zu aktivieren:
- AcceptEx
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- ReadFile
- TransactNamedPipe
- WaitCommEvent
- WriteFile
- WSASendMsg
- WSASendTo
- WSASend
- WSARecvFrom
- LPFN_WSARECVMSG (WSARecvMsg)
- WSARecv