Pool di thread

Un pool di thread è una raccolta di thread di lavoro che eseguono in modo efficiente callback asincroni per conto dell'applicazione. Il pool di thread viene usato principalmente per ridurre il numero di thread dell'applicazione e fornire la gestione dei thread di lavoro. Le applicazioni possono accodamento di elementi di lavoro, associare il lavoro a handle in attesa, accodarsi automaticamente in base a un timer e associarsi a I/O.

Architettura del pool di thread

Le applicazioni seguenti possono trarre vantaggio dall'uso di un pool di thread:

  • Un'applicazione altamente parallela e può inviare in modo asincrono un numero elevato di elementi di lavoro di piccole dimensioni, ad esempio la ricerca di indici distribuiti o l'I/O di rete.
  • Un'applicazione che crea ed elimina definitivamente un numero elevato di thread eseguiti per un breve periodo di tempo. L'uso del pool di thread può ridurre la complessità della gestione dei thread e il sovraccarico necessario per la creazione e la distruzione dei thread.
  • Applicazione che elabora elementi di lavoro indipendenti in background e in parallelo , ad esempio il caricamento di più schede.
  • Applicazione che deve eseguire un'attesa esclusiva sugli oggetti kernel o bloccare gli eventi in ingresso in un oggetto. L'uso del pool di thread può ridurre la complessità della gestione dei thread e migliorare le prestazioni riducendo il numero di commutatori di contesto.
  • Applicazione che crea thread di waiter personalizzati per l'attesa degli eventi.

Il pool di thread originale è stato completamente riprogettato in Windows Vista. Il nuovo pool di thread è migliorato perché fornisce un singolo tipo di thread di lavoro (supporta sia I/O che non I/O), non usa un thread timer, fornisce una singola coda timer e fornisce un thread permanente dedicato. Fornisce anche gruppi di pulizia, prestazioni più elevate, più pool per processo pianificati in modo indipendente e una nuova API del pool di thread.

L'architettura del pool di thread è costituita dai seguenti elementi:

  • Thread di lavoro che eseguono le funzioni di callback
  • Thread waiter che attendono più handle di attesa
  • Una coda di lavoro
  • Pool di thread predefinito per ogni processo
  • Factory di lavoro che gestisce i thread di lavoro

Consigli per iniziare

La nuova API del pool di thread offre maggiore flessibilità e controllo rispetto all'API del pool di thread originale. Tuttavia, ci sono alcune differenze sottili ma importanti. Nell'API originale il ripristino dell'attesa era automatico; nella nuova API, l'attesa deve essere reimpostata in modo esplicito ogni volta. L'API originale ha gestito automaticamente la rappresentazione, trasferendo il contesto di sicurezza del processo chiamante al thread. Con la nuova API, l'applicazione deve impostare in modo esplicito il contesto di sicurezza.

Di seguito sono riportate le procedure consigliate quando si usa un pool di thread:

  • I thread di un processo condividono il pool di thread. Un singolo thread di lavoro può eseguire più funzioni di callback, una alla volta. Questi thread di lavoro vengono gestiti dal pool di thread. Pertanto, non terminare un thread dal pool di thread chiamando TerminateThread sul thread o chiamando ExitThread da una funzione di callback.

  • Una richiesta di I/O può essere eseguita su qualsiasi thread nel pool di thread. L'annullamento dell'I/O in un thread del pool di thread richiede la sincronizzazione perché la funzione di annullamento potrebbe essere eseguita su un thread diverso da quello che gestisce la richiesta di I/O, che può comportare l'annullamento di un'operazione sconosciuta. Per evitare questo problema, fornire sempre la struttura OVERLAPPED con cui è stata avviata una richiesta di I/O durante la chiamata a CancelIoEx per l'I/O asincrona oppure usare la sincronizzazione personalizzata per assicurarsi che nessun altro I/O possa essere avviato nel thread di destinazione prima di chiamare la funzione CancelSynchronousIo o CancelIoEx.

  • Pulire tutte le risorse create nella funzione di callback prima di tornare dalla funzione . Tra cui TLS, contesti di sicurezza, priorità del thread e registrazione COM. Le funzioni di callback devono anche ripristinare lo stato del thread prima di restituire.

  • Mantenere attivi gli handle di attesa e i relativi oggetti associati fino a quando il pool di thread non ha segnalato che è terminato con l'handle.

  • Contrassegnare tutti i thread in attesa di operazioni lunghe (ad esempio scaricamenti di I/O o o pulizia delle risorse) in modo che il pool di thread possa allocare nuovi thread invece di attendere questo thread.

  • Prima di scaricare una DLL che usa il pool di thread, annullare tutti gli elementi di lavoro, I/O, operazioni di attesa e timer e attendere il completamento dell'esecuzione dei callback.

  • Evitare deadlock eliminando le dipendenze tra gli elementi di lavoro e tra i callback, assicurandosi che un callback non sia in attesa del completamento di se stesso e mantenendo la priorità del thread.

  • Non accodare troppi elementi troppo rapidamente in un processo con altri componenti che usano il pool di thread predefinito. Esiste un pool di thread predefinito per processo, incluso Svchost.exe. Per impostazione predefinita, ogni pool di thread ha un massimo di 500 thread di lavoro. Il pool di thread tenta di creare più thread di lavoro quando il numero di thread di lavoro nello stato pronto/in esecuzione deve essere inferiore al numero di processori.

  • Evitare il modello apartment a thread singolo COM, perché non è compatibile con il pool di thread. STA crea lo stato del thread che può influire sull'elemento di lavoro successivo per il thread. STA è in genere di lunga durata e ha affinità di thread, che è l'opposto del pool di thread.

  • Creare un nuovo pool di thread per controllare la priorità e l'isolamento dei thread, creare caratteristiche personalizzate ed eventualmente migliorare la velocità di risposta. Tuttavia, i pool di thread aggiuntivi richiedono più risorse di sistema (thread, memoria kernel). Troppi pool aumentano il potenziale di contesa della CPU.

  • Se possibile, usare un oggetto waitable anziché un meccanismo basato su APC per segnalare un thread del pool di thread. Le API non funzionano anche con i thread del pool di thread come altri meccanismi di segnalazione perché il sistema controlla la durata dei thread del pool di thread, quindi è possibile che un thread venga terminato prima che venga recapitata la notifica.

  • Usare l'estensione del debugger del pool di thread, !tp. Questo comando ha l'utilizzo seguente:

    • flag di indirizzi del pool
    • flag di indirizzo obj
    • flag di indirizzo tqueue
    • indirizzo del cameriere
    • indirizzo di lavoro

    Per pool, waiter e worker, se l'indirizzo è zero, il comando esegue il dump di tutti gli oggetti. Per waiter e worker, omettendo l'indirizzo esegue il dump del thread corrente. Sono definiti i flag seguenti: 0x1 (output a riga singola), 0x2 (membri dump) e 0x4 (coda di lavoro del pool di dump).

Thread Pool API

Uso delle funzioni del pool di thread