Planung im Benutzermodus

Warnung

Ab Windows 11 wird die Planung im Benutzermodus nicht unterstützt. Alle Aufrufe schlagen mit dem Fehler ERROR_NOT_SUPPORTEDfehl.

Die Planung im Benutzermodus (User Mode Scheduling, UMS) ist ein einfacher Mechanismus, mit dem Anwendungen ihre eigenen Threads planen können. Eine Anwendung kann im Benutzermodus zwischen UMS-Threads wechseln, ohne den Systemplaner einzubeziehen, und die Kontrolle über den Prozessor wiedererlangen, wenn ein UMS-Thread im Kernel blockiert. UMS-Threads unterscheiden sich von Fasern darin, dass jeder UMS-Thread über einen eigenen Threadkontext verfügt, anstatt den Threadkontext eines einzelnen Threads frei zu verwenden. Die Möglichkeit, zwischen Threads im Benutzermodus zu wechseln, macht UMS effizienter als Threadpools für die Verwaltung einer großen Anzahl von Kurzzeitarbeitselementen, für die nur wenige Systemaufrufe erforderlich sind.

UMS wird für Anwendungen mit hohen Leistungsanforderungen empfohlen, die viele Threads gleichzeitig auf Mehrprozessor- oder Mehrkernsystemen ausführen müssen. Um UMS nutzen zu können, muss eine Anwendung eine Planerkomponente implementieren, die die UMS-Threads der Anwendung verwaltet und bestimmt, wann sie ausgeführt werden sollen. Entwickler sollten überlegen, ob ihre Anwendungsleistungsanforderungen die Arbeit rechtfertigen, die mit der Entwicklung einer solchen Komponente verbunden ist. Anwendungen mit moderaten Leistungsanforderungen können möglicherweise besser bedient werden, indem der Systemplaner ihre Threads planen kann.

UMS ist für 64-Bit-Anwendungen verfügbar, die unter AMD64- und Itanium-Versionen von Windows 7 und Windows Server 2008 R2 bis Windows 10 Version 21H2 und Windows Server 2022 ausgeführt werden. Dieses Feature ist unter Arm64, 32-Bit-Versionen von Windows oder Windows 11 nicht verfügbar.

Ausführliche Informationen finden Sie in den folgenden Abschnitten:

UMS-Planer

Der UMS-Planer einer Anwendung ist für das Erstellen, Verwalten und Löschen von UMS-Threads und für die Bestimmung des auszuführenden UMS-Threads verantwortlich. Der Planer einer Anwendung führt die folgenden Aufgaben aus:

  • Erstellt einen UMS-Planerthread für jeden Prozessor, auf dem die Anwendung UMS-Workerthreads ausführt.
  • Erstellt UMS-Workerthreads, um die Arbeit der Anwendung auszuführen.
  • Verwaltet eine eigene ready-thread-Warteschlange mit Workerthreads, die ausgeführt werden können, und wählt Threads aus, die basierend auf den Planungsrichtlinien der Anwendung ausgeführt werden sollen.
  • Erstellt und überwacht eine oder mehrere Vervollständigungslisten, in denen das System Threads in die Warteschlange stellt, nachdem sie die Verarbeitung im Kernel abgeschlossen haben. Dazu gehören neu erstellte Workerthreads und Threads, die zuvor bei einem Systemaufruf blockiert wurden, die die Blockierung aufheben.
  • Stellt eine Scheduler-Einstiegspunktfunktion bereit, um Benachrichtigungen aus dem System zu verarbeiten. Das System ruft die Einstiegspunktfunktion auf, wenn ein Planerthread erstellt wird, ein Workerthread einen Systemaufruf blockiert oder ein Workerthread explizit ein Steuerelement ergibt.
  • Führt Bereinigungsaufgaben für Workerthreads aus, die die Ausführung abgeschlossen haben.
  • Führt ein geordnetes Herunterfahren des Schedulers durch, wenn es von der Anwendung angefordert wird.

UMS-Planerthread

Ein UMS-Planerthread ist ein gewöhnlicher Thread, der sich durch Aufrufen der EnterUmsSchedulingMode-Funktion in UMS konvertiert hat. Der Systemplaner bestimmt, wann der UMS-Planerthread basierend auf seiner Priorität im Verhältnis zu anderen bereiten Threads ausgeführt wird. Der Prozessor, auf dem der Planerthread ausgeführt wird, wird von der Affinität des Threads beeinflusst, genau wie bei Nicht-UMS-Threads.

Der Aufrufer von EnterUmsSchedulingMode gibt eine Vervollständigungsliste und eine UmsSchedulerProc-Einstiegspunktfunktion an, die dem UMS-Planerthread zugeordnet werden sollen. Das System ruft die angegebene Einstiegspunktfunktion auf, wenn die Konvertierung des aufrufenden Threads in UMS abgeschlossen ist. Die Scheduler-Einstiegspunktfunktion ist für die Bestimmung der geeigneten nächsten Aktion für den angegebenen Thread verantwortlich. Weitere Informationen finden Sie weiter unten in diesem Thema unter UMS Scheduler Entry Point Function .

Eine Anwendung kann einen UMS-Planerthread für jeden Prozessor erstellen, der zum Ausführen von UMS-Threads verwendet wird. Die Anwendung kann auch die Affinität jedes UMS-Planerthreads für einen bestimmten logischen Prozessor festlegen, wodurch nicht verwandte Threads von der Ausführung auf diesem Prozessor ausgeschlossen werden, wodurch er effektiv für diesen Planerthread reserviert wird. Beachten Sie, dass sich das Festlegen der Threadaffinität auf diese Weise auf die Gesamtleistung des Systems auswirken kann, indem andere Prozesse, die möglicherweise auf dem System ausgeführt werden, ausgehungern. Weitere Informationen zur Threadaffinität finden Sie unter Mehrere Prozessoren.

UMS-Workerthreads, Threadkontexte und Vervollständigungslisten

Ein UMS-Workerthread wird erstellt, indem CreateRemoteThreadEx mit dem attribut PROC_THREAD_ATTRIBUTE_UMS_THREAD aufgerufen und ein UMS-Threadkontext und eine Vervollständigungsliste angegeben werden.

Ein UMS-Threadkontext stellt den UMS-Threadstatus eines Workerthreads dar und wird verwendet, um den Workerthread in UMS-Funktionsaufrufen zu identifizieren. Es wird erstellt, indem CreateUmsThreadContext aufgerufen wird.

Durch Aufrufen der Funktion CreateUmsCompletionList wird eine Vervollständigungsliste erstellt. Eine Vervollständigungsliste empfängt UMS-Workerthreads, die die Ausführung im Kernel abgeschlossen haben und bereit sind, im Benutzermodus auszuführen. Nur das System kann Workerthreads mit einer Vervollständigungsliste in die Warteschlange stellen. Neue UMS-Workerthreads werden automatisch in die Vervollständigungsliste eingereiht, die beim Erstellen der Threads angegeben wurde. Zuvor blockierte Workerthreads werden auch in die Vervollständigungsliste eingereiht, wenn sie nicht mehr blockiert werden.

Jedem UMS-Planerthread ist eine einzelne Vervollständigungsliste zugeordnet. Dieselbe Vervollständigungsliste kann jedoch einer beliebigen Anzahl von UMS-Planerthreads zugeordnet werden, und ein Planerthread kann UMS-Kontexte aus jeder Vervollständigungsliste abrufen, für die ein Zeiger vorhanden ist.

Jede Vervollständigungsliste weist ein zugeordnetes Ereignis auf, das vom System signalisiert wird, wenn mindestens ein Workerthread in eine leere Liste eingereiht wird. Die GetUmsCompletionListEvent-Funktion ruft ein Handle für das Ereignis für eine angegebene Vervollständigungsliste ab. Eine Anwendung kann auf mehrere Vervollständigungslistenereignisse und andere Ereignisse warten, die für die Anwendung sinnvoll sind.

UMS Scheduler Entry Point-Funktion

Die Scheduler-Einstiegspunktfunktion einer Anwendung wird als UmsSchedulerProc-Funktion implementiert. Das System ruft die Scheduler-Einstiegspunktfunktion der Anwendung zu den folgenden Zeitpunkten auf:

  • Wenn ein Nicht-UMS-Thread durch Aufrufen von EnterUmsSchedulingMode in einen UMS-Planerthread konvertiert wird.
  • Wenn ein UMS-Workerthread UmsThreadYield aufruft.
  • Wenn ein UMS-Workerthread für einen Systemdienst wie einen Systemaufruf oder einen Seitenfehler blockiert.

Der Reason-Parameter der UmsSchedulerProc-Funktion gibt den Grund an, aus dem die Einstiegspunktfunktion aufgerufen wurde. Wenn die Einstiegspunktfunktion aufgerufen wurde, weil ein neuer UMS-Planerthread erstellt wurde, enthält der SchedulerParam-Parameter Daten, die vom Aufrufer von EnterUmsSchedulingMode angegeben wurden. Wenn die Einstiegspunktfunktion aufgerufen wurde, weil ein UMS-Workerthread ergibt, enthält der SchedulerParam-Parameter Daten, die vom Aufrufer von UmsThreadYield angegeben wurden. Wenn die Einstiegspunktfunktion aufgerufen wurde, weil ein UMS-Workerthread im Kernel blockiert wurde, ist der SchedulerParam-Parameter NULL.

Die Scheduler-Einstiegspunktfunktion ist für die Bestimmung der geeigneten nächsten Aktion für den angegebenen Thread verantwortlich. Wenn beispielsweise ein Workerthread blockiert wird, kann die Einstiegspunktfunktion des Schedulers den nächsten verfügbaren verfügbaren ums-Workerthread ausführen.

Wenn die Scheduler-Einstiegspunktfunktion aufgerufen wird, sollte der Planer der Anwendung versuchen, alle Elemente in der zugehörigen Vervollständigungsliste abzurufen, indem die DequeueUmsCompletionListItems-Funktion aufgerufen wird. Diese Funktion ruft eine Liste von UMS-Threadkontexten ab, die die Verarbeitung im Kernel abgeschlossen haben und bereit sind, im Benutzermodus auszuführen. Der Planer der Anwendung sollte UMS-Threads nicht direkt aus dieser Liste ausführen, da dies zu unvorhersehbarem Verhalten in der Anwendung führen kann. Stattdessen sollte der Planer alle UMS-Threadkontexte abrufen, indem er die GetNextUmsListItem-Funktion einmal für jeden Kontext aufruft, die UMS-Threadkontexte in die bereite Threadwarteschlange des Planers einfügen und nur dann UMS-Threads aus der bereiten Threadwarteschlange ausführen.

Wenn der Planer nicht auf mehrere Ereignisse warten muss, sollte er DequeueUmsCompletionListItems mit einem nonzero-Timeoutparameter aufrufen, damit die Funktion auf das Vervollständigungslistenereignis wartet, bevor sie zurückgibt. Wenn der Planer auf mehrere Vervollständigungslistenereignisse warten muss, sollte er DequeueUmsCompletionListItems mit einem Timeoutparameter von 0 aufrufen, damit die Funktion sofort zurückgibt, auch wenn die Vervollständigungsliste leer ist. In diesem Fall kann der Planer explizit auf Vervollständigungslistenereignisse warten, z. B. mithilfe von WaitForMultipleObjects.

UMS-Threadausführung

Ein neu erstellter UMS-Workerthread wird in die angegebene Vervollständigungsliste eingereiht und wird erst ausgeführt, wenn der UMS-Planer der Anwendung ihn zur Ausführung auswählt. Dies unterscheidet sich von Nicht-UMS-Threads, für die der Systemplaner die Ausführung automatisch plant, es sei denn, der Aufrufer erstellt explizit den angehaltenen Thread.

Der Planer führt einen Workerthread aus, indem er ExecuteUmsThread mit dem UMS-Kontext des Workerthreads aufruft. Ein UMS-Workerthread wird so lange ausgeführt, bis er durch Aufrufen der UmsThreadYield-Funktion blöcke oder beendet wird.

BEWÄHRTE METHODEN FÜR UMS

Anwendungen, die UMS implementieren, sollten die folgenden bewährten Methoden befolgen:

  • Die zugrunde liegenden Strukturen für UMS-Threadkontexte werden vom System verwaltet und sollten nicht direkt geändert werden. Verwenden Sie stattdessen QueryUmsThreadInformation und SetUmsThreadInformation , um Informationen zu einem UMS-Workerthread abzurufen und festzulegen.
  • Um Deadlocks zu verhindern, sollte der UMS-Planerthread keine Sperren für UMS-Workerthreads freigeben. Dies umfasst sowohl von der Anwendung erstellte Sperren als auch Systemsperren, die indirekt durch Vorgänge wie das Zuweisen aus dem Heap oder das Laden von DLLs abgerufen werden. Angenommen, der Planer führt einen UMS-Workerthread aus, der eine DLL lädt. Der Workerthread ruft die Ladesperre und die Blöcke ab. Das System ruft die Scheduler-Einstiegspunktfunktion auf, die dann eine DLL lädt. Dies verursacht einen Deadlock, da die Ladeprogrammsperre bereits gehalten wird und erst freigegeben werden kann, wenn der erste Thread die Blockierung aufgehoben hat. Um dieses Problem zu vermeiden, delegieren Sie Arbeiten, die möglicherweise Sperren mit UMS-Workerthreads gemeinsam nutzen, an einen dedizierten UMS-Workerthread oder einen Nicht-UMS-Thread.
  • UMS ist am effizientesten, wenn die meiste Verarbeitung im Benutzermodus erfolgt. Vermeiden Sie nach Möglichkeit Systemaufrufe in UMS-Workerthreads.
  • UMS-Workerthreads sollten nicht davon ausgehen, dass der Systemplaner verwendet wird. Diese Annahme kann subtile Auswirkungen haben; Wenn beispielsweise ein Thread im unbekannten Code eine Threadpriorität oder -affinität festlegt, kann der UMS-Planer ihn trotzdem außer Kraft setzen. Code, der davon ausgeht, dass der Systemplaner verwendet wird, verhält sich möglicherweise nicht wie erwartet und kann beim Aufrufen durch einen UMS-Thread unterbrochen werden.
  • Möglicherweise muss das System den Threadkontext eines UMS-Workerthreads sperren. Beispielsweise kann ein asynchroner Prozeduraufruf im Kernelmodus den Kontext des UMS-Threads ändern, sodass der Threadkontext gesperrt werden muss. Wenn der Planer versucht, den UMS-Threadkontext auszuführen, während er gesperrt ist, schlägt der Aufruf fehl. Dieses Verhalten ist designmäßig, und der Planer sollte so konzipiert sein, dass er den Zugriff auf den UMS-Threadkontext erneut versucht.