Condividi tramite


pianificazione User-Mode

Avvertimento

A partire da Windows 11, la pianificazione in modalità utente non è supportata. Tutte le chiamate hanno esito negativo con l'errore ERROR_NOT_SUPPORTED.

La pianificazione in modalità utente (UMS) è un meccanismo leggero che le applicazioni possono usare per pianificare i propri thread. Un'applicazione può passare da thread UMS in modalità utente senza coinvolgere l'utilità di pianificazione di sistema e recuperare il controllo del processore se un thread UMS si blocca nel kernel. I thread UMS differiscono dai fili in quanto ogni thread UMS ha un proprio contesto di thread anziché condividere il contesto di un singolo thread. La possibilità di passare da un thread all'altro in modalità utente rende UMS più efficiente di pool di thread per la gestione di un numero elevato di elementi di lavoro a breve durata che richiedono poche chiamate di sistema.

UMS è consigliato per le applicazioni con requisiti a prestazioni elevate che devono eseguire in modo efficiente molti thread contemporaneamente su sistemi multiprocessore o multicore. Per sfruttare lo scheduling in modalità utente (UMS), un'applicazione deve implementare un componente scheduler che gestisce i thread UMS dell'applicazione e determina quando devono essere eseguiti. Gli sviluppatori devono valutare se i requisiti di prestazioni dell'applicazione giustificano il lavoro coinvolto nello sviluppo di tale componente. Le applicazioni con requisiti di prestazioni moderate potrebbero essere meglio gestite consentendo all'utilità di pianificazione del sistema di pianificare i thread.

UMS è disponibile per le applicazioni a 64 bit in esecuzione in versioni AMD64 e Itanium di Windows 7 e Windows Server 2008 R2 fino a Windows 10 versione 21H2 e Windows Server 2022. Questa funzionalità non è disponibile in Arm64, versioni a 32 bit di Windows o in Windows 11.

Per informazioni dettagliate, vedere le sezioni seguenti:

UMS Scheduler

L'utilità di pianificazione UMS di un'applicazione è responsabile della creazione, della gestione e dell'eliminazione di thread di messaggistica unificata e della determinazione del thread UMS da eseguire. Il pianificatore di un'applicazione esegue le attività seguenti:

  • Crea un thread del pianificatore UMS per ogni processore su cui l'applicazione esegue thread di lavoro UMS.
  • Crea thread di lavoro UMS per eseguire il lavoro dell'applicazione.
  • Gestisce la propria coda ready-thread dei thread di lavoro pronti per l'esecuzione e seleziona i thread da eseguire in base ai criteri di pianificazione dell'applicazione.
  • Crea e monitora uno o più elenchi di completamento dove i thread del sistema vengono accodati al termine dell'elaborazione nel kernel. Questi includono thread di lavoro appena creati e thread precedentemente bloccati da una chiamata di sistema che vengono sbloccati.
  • Fornisce una funzione di ingresso del pianificatore per gestire le notifiche dal sistema. Il sistema chiama la funzione del punto di ingresso quando viene creato un thread dello scheduler, un thread di lavoro si blocca in una chiamata di sistema o un thread di lavoro cede esplicitamente il controllo.
  • Esegue attività di pulizia per i thread di lavoro che hanno terminato l'esecuzione.
  • Esegue un arresto ordinato del pianificatore quando richiesto dall'applicazione.

Thread del pianificatore UMS

Un thread dell'utilità di pianificazione UMS è un thread ordinario che si è convertito in UMS chiamando la funzione EnterUmsSchedulingMode. L'utilità di pianificazione di sistema determina quando il thread dell'UMS scheduler viene eseguito in base alla priorità rispetto ad altri thread pronti. Il processore su cui viene eseguito il thread del gestore è influenzato dall'affinità del thread, come avviene anche per i thread non UMS.

Il chiamante di EnterUmsSchedulingMode specifica un elenco di completamento e una funzione di punto di ingresso UmsSchedulerProc da associare al thread dell'utilità di pianificazione UMS. Il sistema chiama la funzione del punto di ingresso specificata al termine della conversione del thread chiamante in UMS. La funzione punto di ingresso del pianificatore è responsabile nel determinare l'azione successiva appropriata per il thread specificato. Per ulteriori informazioni, vedere Funzione di Punto di Ingresso del Pianificatore UMS più avanti in questo argomento.

Un'applicazione potrebbe creare un thread dell'utilità di pianificazione di messaggistica unificata per ogni processore che verrà usato per eseguire thread di messaggistica unificata. L'applicazione potrebbe anche impostare l'affinità di ogni thread dell'utilità di pianificazione UMS per un processore logico specifico, che tende a escludere thread non correlati dall'esecuzione su tale processore, riservandolo in modo efficace per tale thread dell'utilità di pianificazione. Tenere presente che l'impostazione dell'affinità dei thread in questo modo può influire sulle prestazioni complessive del sistema, privando altri processi che potrebbero essere in esecuzione nel sistema. Per altre informazioni sull'affinità di thread, vedere Processori Multipli.

Thread di lavoro di messaggistica unificata, contesti di thread ed elenchi di completamento

Viene creato un thread di lavoro UMS chiamando CreateRemoteThreadEx con l'attributo PROC_THREAD_ATTRIBUTE_UMS_THREAD e specificando un contesto del thread UMS e un elenco di completamento.

Un contesto di thread UMS rappresenta lo stato del thread UMS di un thread di lavoro ed è usato per identificare il thread di lavoro nelle chiamate di funzione UMS. Viene creato chiamando CreateUmsThreadContext.

Viene creato un elenco di completamento chiamando la funzioneCreateUmsCompletionList. Un elenco di completamento riceve thread di lavoro UMS che hanno completato l'esecuzione nel kernel e sono pronti per l'esecuzione in modalità utente. Solo il sistema può accodare thread di lavoro a un elenco di completamento. I nuovi thread di lavoro UMS vengono accodati automaticamente all'elenco di completamento specificato al momento della creazione dei thread. Anche i thread di lavoro che erano bloccati in precedenza vengono accodati all'elenco di completamento quando non sono più bloccati.

Ogni thread del pianificatore UMS è associato a un singolo elenco di completamento. Tuttavia, lo stesso elenco di completamento può essere associato a un numero qualsiasi di thread del pianificatore UMS, e un thread del pianificatore può recuperare i contesti UMS da qualsiasi elenco di completamento per il quale dispone di un puntatore.

Ogni lista di completamento ha un evento associato indicato dal sistema quando il sistema accoda uno o più thread di lavoro a una lista vuota. La funzione GetUmsCompletionListEvent recupera un handle per l'evento per un elenco di completamento specificato. Un'applicazione può attendere più di un evento nella lista di completamento, insieme ad altri eventi rilevanti per l'applicazione.

Funzione punto di ingresso del pianificatore UMS

La funzione del punto di ingresso dello scheduler di un'applicazione viene implementata come una funzione UmsSchedulerProc. Il sistema chiama la funzione del punto di ingresso del scheduler dell'applicazione nei momenti seguenti.

  • Quando un thread non UMS viene convertito in un thread del pianificatore UMS chiamando EnterUmsSchedulingMode.
  • Quando un thread di lavoro di messaggistica unificata chiama UmsThreadYield.
  • Quando un thread di lavoro di messaggistica unificata si blocca in un servizio di sistema, ad esempio una chiamata di sistema o un errore di pagina.

Il parametro Reason della funzione UmsSchedulerProc specifica il motivo per cui è stata chiamata la funzione del punto di ingresso. Se la funzione del punto di ingresso è stata chiamata perché è stato creato un nuovo thread dell'utilità di pianificazione UMS, il parametro SchedulerParam contiene i dati specificati dal chiamante di EnterUmsSchedulingMode. Se la funzione del punto di ingresso è stata chiamata perché è stato generato un thread di lavoro UMS, il parametro SchedulerParam contiene i dati specificati dal chiamante di UmsThreadYield. Se la funzione del punto di ingresso è stata chiamata perché un thread di lavoro UMS si è bloccato nel kernel, il parametro SchedulerParam è NULL.

La funzione di punto di ingresso del pianificatore è responsabile di determinare l'azione successiva appropriata per il thread specificato. Ad esempio, se un thread di lavoro è bloccato, la funzione del punto di ingresso dell'utilità di pianificazione potrebbe eseguire il successivo thread di lavoro UMS pronto disponibile.

Quando viene chiamata la funzione di punto di ingresso del pianificatore, il pianificatore dell'applicazione deve provare a ottenere tutti gli elementi nell'elenco di completamento associato chiamando la funzione DequeueUmsCompletionListItems. Questa funzione recupera un elenco di contesti di thread UMS che hanno terminato l'elaborazione nel kernel e sono pronti per l'esecuzione in modalità utente. L'utilità di pianificazione dell'applicazione non deve eseguire thread di messaggistica unificata direttamente da questo elenco perché ciò può causare un comportamento imprevedibile nell'applicazione. L'utilità di pianificazione deve invece recuperare tutti i contesti del thread di messaggistica unificata chiamando la funzione GetNextUmsListItem una volta per ogni contesto, inserire i contesti del thread UMS nella coda dei thread pronti dell'utilità di pianificazione e quindi eseguire solo i thread UMS dalla coda dei thread pronti.

Se l'utilità di pianificazione non deve attendere più eventi, deve chiamare DequeueUmsCompletionListItems con un parametro di timeout diverso da zero in modo che la funzione attenda l'evento dell'elenco di completamento prima di restituire. Se l'utilità di pianificazione deve attendere più eventi dell'elenco di completamento, deve chiamare DequeueUmsCompletionListItems con un parametro di timeout pari a zero in modo che la funzione venga restituita immediatamente, anche se l'elenco di completamento è vuoto. In questo caso, il scheduler può attendere in modo esplicito gli eventi nella lista di completamento, ad esempio usando WaitForMultipleObjects.

Esecuzione del thread UMS

Un thread di lavoro UMS appena creato viene accodato all'elenco di completamento specificato e non inizia l'esecuzione fino a quando l'utilità di pianificazione UMS dell'applicazione lo seleziona per l'esecuzione. Ciò differisce dai thread non UMS, che il pianificatore di sistema programma automaticamente per l'esecuzione, a meno che il chiamante non crei esplicitamente il thread in stato di sospensione.

Lo scheduler esegue un thread di lavoro chiamando ExecuteUmsThread, con il contesto UMS del thread di lavoro. Un thread di lavoro UMS viene eseguito fino a quando non cede chiamando la funzione UmsThreadYield, si blocca o termina.

Linee guida migliori per il Sistema di Messaggistica Unificata.

Le applicazioni che implementano UMS devono seguire queste procedure consigliate:

  • Le strutture sottostanti per i contesti di thread di messaggistica unificata vengono gestite dal sistema e non devono essere modificate direttamente. Usare invece QueryUmsThreadInformation e SetUmsThreadInformation per recuperare e impostare informazioni su un thread di lavoro di messaggistica unificata.
  • Per evitare deadlock, il thread dell'UMS scheduler non deve condividere i blocchi con i thread di lavoro dell'UMS. Sono inclusi sia i blocchi creati dall'applicazione che i blocchi di sistema acquisiti indirettamente da operazioni come l'allocazione dall'heap o il caricamento di DLL. Si supponga, ad esempio, che lo scheduler esegua un thread di lavoro UMS che carica una DLL. Il thread di lavoro acquisisce il blocco del caricatore e si blocca. Il sistema chiama la funzione del punto di ingresso del pianificatore, la quale carica una DLL. Ciò causa un deadlock, perché il blocco del caricatore è già attivo e non può essere rilasciato fino a quando il primo thread viene sbloccato. Per evitare questo problema, delegare il lavoro che potrebbe condividere blocchi con i thread UMS a un thread UMS dedicato o a un thread non UMS.
  • UMS è più efficiente quando la maggior parte dell'elaborazione viene eseguita in modalità utente. Quando possibile, evitare di effettuare chiamate di sistema nei thread di lavoro di messaggistica unificata.
  • I thread di lavoro UMS non devono presupporre che venga usato il scheduler di sistema. Questo presupposto può avere effetti sottili; Ad esempio, se un thread nel codice sconosciuto imposta una priorità di thread o un'affinità, l'utilità di pianificazione UMS potrebbe comunque eseguirne l'override. Il codice che presuppone che l'utilità di pianificazione del sistema venga usata potrebbe non comportarsi come previsto e potrebbe interrompersi quando viene chiamato da un thread UMS.
  • Il sistema potrebbe dover bloccare il contesto del thread di un thread di lavoro UMS. Ad esempio, una chiamata asincrona in modalità kernel (APC) potrebbe modificare il contesto del thread UMS, quindi il contesto del thread deve essere bloccato. Se l'utilità di pianificazione tenta di eseguire il contesto del thread di messaggistica unificata mentre è bloccata, la chiamata avrà esito negativo. Questo comportamento è previsto dalla progettazione e il pianificatore deve essere progettato per ritentare l'accesso al contesto del thread UMS.