Threadpools

Ein Threadpool ist eine Sammlung von Arbeitsthreads, die asynchrone Rückrufe im Auftrag der Anwendung effizient ausführen. Der Threadpool wird in erster Linie verwendet, um die Anzahl der Anwendungsthreads zu reduzieren und die Verwaltung der Arbeitsthreads bereitzustellen. Anwendungen können Arbeitsaufgaben in eine Warteschlange stellen, Arbeit mit Handles verknüpfen, auf die gewartet werden kann, automatisch basierend auf einem Timer in eine Warteschlange stellen und mit E/A binden.

Threadpoolarchitektur

Die folgenden Anwendungen können von der Verwendung eines Threadpools profitieren:

  • Eine Anwendung, die hochgradig parallel ist und eine große Anzahl kleiner Arbeitsaufgaben asynchron verteilen kann (z. B. verteilte Indexsuche oder Netzwerk-E/A).
  • Eine Anwendung, die eine große Anzahl von Threads erstellt und löscht, die jeweils nur kurze Zeit aktiv sind. Die Verwendung des Threadpools kann die Komplexität der Threadverwaltung und den Aufwand für die Threaderstellung und -löschung reduzieren.
  • Eine Anwendung, die unabhängige Arbeitsaufgaben im Hintergrund und parallel verarbeitet (z. B. mehrere Registerkarten laden).
  • Eine Anwendung, die ausschließlich auf Kernel-Objekte warten oder eingehende Ereignisse auf einem Objekt blockieren muss. Durch die Verwendung des Threadpools kann die Komplexität der Threadverwaltung reduziert und die Leistung durch die Reduzierung der Anzahl der Kontextwechsel erhöht werden.
  • Eine Anwendung, die benutzerdefinierte Wartethreads zum Warten auf Ereignisse erstellt.

Der ursprüngliche Threadpool wurde in Windows Vista vollständig neu gestaltet. Der neue Threadpool wurde verbessert, da er einen einzelnen Workerthreadtyp bereitstellt (sowohl E/A als auch Nicht-E/A unterstützt), keinen Zeitgeberthread verwendet, eine einzelne Zeitgeberwarteschlange bereitstellt und einen dedizierten beständigen Thread bereitstellt. Er bietet außerdem Bereinigungsgruppen, höhere Leistung, mehrere Pools pro Prozess, die unabhängig geplant werden, und eine neue Threadpool-API.

Der Threadpool umfasst die folgenden Komponenten:

  • Arbeitsthreads, die die Rückruffunktionen ausführen
  • Wartethreads, die auf mehrere Wartehandles warten
  • Eine Arbeitswarteschlange
  • Ein Standardthreadpool für jeden Prozess
  • Eine Arbeitsfactory, die die Arbeitsthreads verwaltet

Bewährte Methoden

Die neue Threadpool-API bietet mehr Flexibilität und Kontrolle als die ursprüngliche Threadpool-API. Es gibt jedoch ein paar kleine, aber wichtige Unterschiede. In der ursprünglichen API war die Wartezurücksetzung automatisch; in der neuen API muss die Wartezeit jedes Mal explizit zurückgesetzt werden. Die ursprüngliche API behandelte die Identitätswechsel automatisch und übertrug den Sicherheitskontext des aufrufenden Prozesses auf den Thread. Mit der neuen API muss die Anwendung explizit den Sicherheitskontext festlegen.

Es folgen bewährte Methoden bei der Verwendung eines Threadpools:

  • Die Threads eines Prozesses teilen den Threadpool. Ein einzelner Arbeitsthread kann mehrere Rückruffunktionen gleichzeitig ausführen. Diese Arbeitsthreads werden vom Threadpool verwaltet. Beenden Sie daher keinen Thread aus dem Threadpool, indem Sie TerminateThread für den Thread oder ExitThread aus einer Rückruffunktion aufrufen.

  • Eine E/A-Anforderung kann auf einem beliebigen Thread im Threadpool ausgeführt werden. Das Abbrechen von E/A-Vorgängen in einem Threadpoolthread erfordert eine Synchronisierung, da die cancel-Funktion möglicherweise in einem anderen Thread ausgeführt wird als dem, der die E/A-Anforderung verarbeitet, was zum Abbruch eines unbekannten Vorgangs führen kann. Um dies zu vermeiden, stellen Sie immer OVERLAPPED-Struktur bereit, mit der eine E/A-Anforderung beim Aufrufen von CancelIoEx für asynchrone E/A initiiert wurde, oder verwenden Sie ihre eigene Synchronisierung, um sicherzustellen, dass keine andere E/A im Zielthread gestartet werden kann, bevor Sie entweder die Funktion CancelSynchronousIo oder die Funktion CancelIoEx aufrufen.

  • Bereinigen Sie alle in der Rückruffunktion erstellten Ressourcen, bevor Sie die Funktion verlassen. Dazu gehören TLS, Sicherheitskontexte, Threadpriorität und COM-Registrierung. Rückruffunktionen müssen auch den Threadzustand wiederherstellen, bevor sie zurückgegeben werden.

  • Halten Sie die Warte-Handles und die damit verbundenen Objekte aktiv, bis der Threadpool signalisiert hat, dass er mit dem Handle fertig ist.

  • Markieren Sie alle Threads, die auf langwierige Vorgänge warten (z. B. E/A-Löschvorgänge oder Ressourcenbereinigung), damit der Threadpool neue Threads zuordnen kann, anstatt auf diese zu warten.

  • Vor dem Entladen einer DLL, die den Threadpool verwendet, brechen Sie alle Arbeitsaufgaben, E/A, Wartevorgänge und Zeitgeber ab, und warten Sie, bis Rückrufe ausgeführt werden.

  • Vermeiden Sie Deadlocks, indem Sie Abhängigkeiten zwischen Arbeitsaufgaben und zwischen Rückrufen beseitigen, indem Sie sicherstellen, dass ein Rückruf nicht auf sich selbst wartet, um abgeschlossen zu werden, und indem Sie die Threadpriorität beibehalten.

  • Fügen Sie nicht zu viele Elemente zu schnell in einen Prozess mit anderen Komponenten ein, die den Standardthreadpool verwenden. Pro Prozess gibt es einen Standardthreadpool, einschließlich Svchost.exe. Standardmäßig verfügt jeder Threadpool über maximal 500 Arbeitsthreads. Der Threadpool versucht, mehr Arbeitsthreads zu erstellen, wenn die Anzahl der Arbeitsthreads im Zustand „Bereit/Wird ausgeführt“ kleiner als die Anzahl der Prozessoren sein muss.

  • Vermeiden Sie das COM-Einzelthread-Apartmentmodell, da es nicht mit dem Threadpool kompatibel ist. STA erstellt den Threadzustand, der sich auf die nächste Arbeitsaufgabe für den Thread auswirken kann. STA ist in der Regel langlebig und hat Threadaffinität, was das Gegenteil des Threadpools ist.

  • Erstellen Sie einen neuen Threadpool, um die Threadpriorität und -isolation zu steuern, benutzerdefinierte Merkmale zu erstellen und möglicherweise die Reaktionsfähigkeit zu verbessern. Zusätzliche Threadpools erfordern jedoch mehr Systemressourcen (Threads, Kernelspeicher). Zu viele Pools erhöhen das Potenzial für CPU-Konflikte.

  • Verwenden Sie nach Möglichkeit ein Objekt, auf das gewartet werden kann, anstelle eines APC-basierten Mechanismus, um einen Threadpoolthread zu signalisieren. APCs funktionieren nicht genauso gut mit Threadpoolthreads wie andere Signalmechanismen, da das System die Lebensdauer von Threadpoolthreads steuert, sodass es möglich ist, dass ein Thread beendet wird, bevor die Benachrichtigung übermittelt wird.

  • Verwenden Sie die Threadpool-Debuggererweiterung !tp. Dieser Befehl hat folgende Verwendung:

    • pool address flags
    • obj address flags
    • tqueue address flags
    • waiter address
    • worker address

    Für Pool, Waiter und Worker: wenn die Adresse null ist, werden alle Objekte gelöscht. Für Waiter und Worker wird der aktuelle Thread gelöscht. Die folgenden Flags sind definiert: 0x1 (einzeilige Ausgabe), 0x2 (Speicherabbildmitglieder) und 0x4 (Speicherabbild-Arbeitspufferwarteschlange).

Threadpool-API

Verwenden der Threadpoolfunktionen