Threadpools

Ein Threadpool ist eine Sammlung von Arbeitsthreads, die asynchrone Rückrufe im Namen der Anwendung effizient ausführen. Der Threadpool wird hauptsächlich verwendet, um die Anzahl der Anwendungsthreads zu reduzieren und die Verwaltung der Workerthreads bereitzustellen. Anwendungen können Arbeitselemente in die Warteschlange stellen, Arbeit wartebaren Handles zuordnen, automatisch basierend auf einem Timer in die Warteschlange stellen und 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 Arbeitselemente asynchron verteilen kann (z. B. die Suche nach verteilten Indexen oder Netzwerk-E/A).
  • Eine Anwendung, die eine große Anzahl von Threads erstellt und zerstört, die jeweils für kurze Zeit ausgeführt werden. Die Verwendung des Threadpools kann die Komplexität der Threadverwaltung und den Mehraufwand für die Erstellung und Zerstörung von Threads reduzieren.
  • Eine Anwendung, die unabhängige Arbeitselemente im Hintergrund und parallel verarbeitet (z. B. das Laden mehrerer Registerkarten).
  • Eine Anwendung, die ein exklusives Warten auf Kernelobjekte ausführen oder eingehende Ereignisse für ein Objekt blockieren muss. Die Verwendung des Threadpools kann die Komplexität der Threadverwaltung verringern und die Leistung erhöhen, indem die Anzahl der Kontextwechsel reduziert wird.
  • Eine Anwendung, die benutzerdefinierte Kellnerthreads erstellt, um auf Ereignisse zu warten.

Der ursprüngliche Threadpool wurde in Windows Vista vollständig neu strukturiert. Der neue Threadpool wurde verbessert, da er einen einzelnen Workerthreadtyp bereitstellt (unterstützt sowohl E/A als auch Nicht-E/A), keinen Timerthread verwendet, eine einzelne Timerwarteschlange bereitstellt und einen dedizierten persistenten Thread bereitstellt. Außerdem bietet es sauber-up-Gruppen, eine höhere Leistung, mehrere unabhängig geplante Pools pro Prozess und eine neue Threadpool-API.

Die Threadpoolarchitektur besteht aus folgenden Elementen:

  • Workerthreads, die die Rückruffunktionen ausführen
  • Kellnerthreads, die auf mehrere Wartehandles warten
  • Eine Arbeitswarteschlange
  • Ein Standardthreadpool für jeden Prozess
  • Eine Workerfactory, 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 einige subtile, aber wichtige Unterschiede. In der ursprünglichen API wurde die Wartezurücksetzung automatisch durchgeführt. in der neuen API muss die Wartezeit jedes Mal explizit zurückgesetzt werden. Die ursprüngliche API hat den Identitätswechsel automatisch verarbeitet und den Sicherheitskontext des aufrufenden Prozesses an den Thread übertragen. Mit der neuen API muss die Anwendung den Sicherheitskontext explizit festlegen.

Im Folgenden sind bewährte Methoden für die Verwendung eines Threadpools aufgeführt:

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

  • Eine E/A-Anforderung kann für jeden Thread im Threadpool ausgeführt werden. Zum Abbrechen der E/A in einem Threadpoolthread ist eine Synchronisierung erforderlich, da die Cancel-Funktion möglicherweise in einem anderen Thread als dem ausgeführt wird, der die E/A-Anforderung verarbeitet, was zum Abbruch eines unbekannten Vorgangs führen kann. Um dies zu vermeiden, geben Sie immer die OVERLAPPED-Struktur an, mit der beim Aufrufen von CancelIoEx für asynchrone E/A eine E/A-Anforderung initiiert wurde, oder verwenden Sie Ihre eigene Synchronisierung, um sicherzustellen, dass keine anderen E/A-Vorgänge im Zielthread gestartet werden können, bevor sie entweder die CancelSynchronousIo - oder CancelIoEx-Funktion aufrufen.

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

  • Warten Sie Handles und die zugehörigen Objekte aktiv, bis der Threadpool signalisiert hat, dass er mit dem Handle fertig ist.

  • Markieren Sie alle Threads, die auf lange Vorgänge warten (z. B. E/A-Leerungen oder Ressourcenbereinigung), damit der Threadpool neue Threads zuordnen kann, anstatt auf diesen zu warten.

  • Brechen Sie vor dem Entladen einer DLL, die den Threadpool verwendet, alle Arbeitselemente, E/A-Vorgänge, Wartevorgänge und Zeitgeber ab, und warten Sie, bis die Rückrufe abgeschlossen sind.

  • Vermeiden Sie Deadlocks, indem Sie Abhängigkeiten zwischen Arbeitselementen und zwischen Rückrufen beseitigen, sicherstellen, dass ein Rückruf nicht auf den Abschluss wartet, und indem Sie die Threadpriorität beibehalten.

  • Führen Sie in einem Prozess nicht zu schnell eine Warteschlange mit anderen Komponenten mithilfe des Standardthreadpools durch. Es gibt einen Standardthreadpool pro Prozess, 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 Status "Bereit/Ausgeführt" kleiner als die Anzahl der Prozessoren sein muss.

  • Vermeiden Sie das COM-Singlethread-Apartmentmodell, da es mit dem Threadpool nicht kompatibel ist. STA erstellt den Threadzustand, der sich auf das nächste Arbeitselement für den Thread auswirken kann. STA ist im Allgemeinen 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 wartebares Objekt anstelle eines APC-basierten Mechanismus, um einen Threadpoolthread zu signalisieren. APCs funktionieren bei Threadpoolthreads nicht so gut wie andere Signalisierungsmechanismen, 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 Threadpooldebuggererweiterung !tp. Dieser Befehl hat die folgende Verwendung:

    • Pooladressflags
    • obj-Adressflags
    • Queue-Adressflags
    • Waiter-Adresse
    • Workeradresse

    Wenn die Adresse für Pool, Kellner und Worker null ist, werden mit dem Befehl alle Objekte abgespeichert. Für Waiter und Worker wird durch Weglassen der Adresse der aktuelle Thread abgebildet. Die folgenden Flags sind definiert: 0x1 (einzeilige Ausgabe), 0x2 (Dumpmber) und 0x4 (Arbeitswarteschlange des Speicherabbildpools).

Threadpool-API

Verwenden der Threadpoolfunktionen