Quando utilizzare il pool di thread in C #?

Ho cercato di imparare la programmazione multi-thread in C # e sono confuso su quando è meglio usare un pool di thread e creare i miei thread. Un libro raccomanda l’uso di un pool di thread solo per piccole attività (qualunque cosa ciò significhi), ma non riesco a trovare alcuna linea guida reale. Quali sono alcune considerazioni che usi quando prendi questa decisione di programmazione?

Se si dispone di molte attività logiche che richiedono un’elaborazione costante e si desidera che ciò avvenga in parallelo, utilizzare lo strumento di pianificazione pool +.

Se è necessario eseguire contemporaneamente le attività relative all’IO, come il download di materiale da server remoti o l’accesso al disco, ma è necessario farlo dire una volta ogni pochi minuti, quindi creare i propri thread e ucciderli una volta terminato.

Modifica: A proposito di alcune considerazioni, utilizzo i pool di thread per l’accesso al database, fisica / simulazione, AI (giochi) e per le attività con script eseguite su macchine virtuali che elaborano molte attività definite dall’utente.

Normalmente un pool è composto da 2 thread per processore (quindi 4 probabilmente al giorno d’oggi), tuttavia è ansible impostare la quantità di thread che si desidera, se si sa di quanti ne avete bisogno.

Modifica: La ragione per creare i propri thread è a causa delle modifiche al contesto, (questo è il momento in cui i thread devono entrare e uscire dal processo, insieme alla loro memoria). Avendo cambiamenti di contesto inutili, diciamo quando non stai usando i tuoi thread, lasciandoli seduti come si potrebbe dire, può facilmente dimezzare le prestazioni del tuo programma (ad esempio hai 3 thread dormienti e 2 thread attivi). Quindi, se quei thread di download stanno solo aspettando, stanno consumando tonnellate di CPU e raffreddando la cache per la tua vera applicazione

Ti suggerirei di utilizzare un pool di thread in C # per gli stessi motivi di qualsiasi altra lingua.

Quando si desidera limitare il numero di thread in esecuzione o non si desidera l’overhead di creazione e distruzione di essi, utilizzare un pool di thread.

Con piccoli compiti, il libro che leggi è un’attività con una breve durata. Se occorrono dieci secondi per creare un thread che gira solo per un secondo, questo è un punto in cui dovresti usare i pool (ignora i miei dati reali, è il rapporto che conta).

Altrimenti, trascorri la maggior parte del tuo tempo creando e distruggendo discussioni piuttosto che semplicemente facendo il lavoro che intendono svolgere.

Ecco un bel riassunto del pool di thread in. Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-readread-thread.aspx

Il post ha anche alcuni punti su quando non si dovrebbe usare il pool di thread e avviare invece il proprio thread.

Consiglio vivamente di leggere questo e-book gratuito: Threading in C # di Joseph Albahari

Leggi almeno la sezione “Guida introduttiva”. L’e-book offre un’ottima introduzione e include anche numerose informazioni avanzate sul threading.

Sapere se usare o meno il pool di thread è solo l’inizio. Successivamente dovrai determinare quale metodo di inserimento del pool di thread è più adatto alle tue esigenze:

  • Task Parallel Library (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • Delegati asincroni
  • BackgroundWorker

Questo e-book spiega tutti questi e consiglia quando usarli contro creare il proprio thread.

Il pool di thread è progettato per ridurre il cambio di contesto tra i thread. Considera un processo con diversi componenti in esecuzione. Ognuno di questi componenti potrebbe creare thread di lavoro. Più thread nel processo, più tempo viene sprecato per il cambio di contesto.

Ora, se ognuno di questi componenti fosse in coda agli oggetti nel pool di thread, si avrebbe molto meno overhead di commutazione del contesto.

Il pool di thread è progettato per ottimizzare il lavoro svolto sulle CPU (o core CPU). Ecco perché, per impostazione predefinita, il pool di thread fa girare più thread per processore.

Ci sono alcune situazioni in cui non si vorrebbe usare il pool di thread. Se stai aspettando l’I / O, o stai aspettando un evento, ecc. Allora leghi quel filo del pool di thread e non può essere usato da nessun altro. La stessa idea si applica alle attività di lunga durata, anche se ciò che costituisce un’attività a lungo termine è soggettivo.

Anche Pax Diablo è un buon punto. La rotazione dei thread non è gratuita. Ci vuole tempo e consumano memoria aggiuntiva per il loro spazio di stack. Il pool di thread riutilizzerà i thread per ammortizzare questo costo.

Nota: è stato chiesto di utilizzare un thread pool di thread per scaricare dati o eseguire I / O su disco. Non dovresti usare un thread thread thread per questo (per i motivi che ho descritto sopra). Utilizzare invece l’I / O asincrono (ovvero i metodi BeginXX e EndXX). Per un FileStream che sarebbe BeginRead e EndRead . Per una HttpWebRequest che sarebbe BeginGetResponse e EndGetResponse . Sono più complicati da usare, ma sono il modo corretto per eseguire I / O multi-thread.

Fai attenzione al pool di thread .NET per le operazioni che potrebbero bloccare qualsiasi parte significativa, variabile o sconosciuta del loro trattamento, poiché è soggetta a thread di fame. Prendi in considerazione l’utilizzo delle estensioni parallele .NET, che forniscono un buon numero di astrazioni logiche rispetto alle operazioni con thread. Includono anche un nuovo schedulatore, che dovrebbe essere un miglioramento di ThreadPool. Vedi qui

Un motivo per utilizzare il pool di thread solo per attività di piccole dimensioni è che esiste un numero limitato di thread del pool di thread. Se uno viene utilizzato per un lungo periodo di tempo, impedisce che quel thread venga utilizzato da un altro codice. Se ciò accade molte volte, il pool di thread può esaurirsi.

L’utilizzo del pool di thread può avere effetti delicati: alcuni timer .NET utilizzano thread di thread thread e non sparano, ad esempio.

Per le massime prestazioni con le unità in esecuzione simultanee, scrivi il tuo pool di thread, in cui viene creato un pool di oggetti Thread all’avvio e vai al blocco (precedentemente sospeso), in attesa di un contesto da eseguire (un object con un’interfaccia standard implementata da il tuo codice).

Tanti articoli su Tasks vs. Threads rispetto a .NET ThreadPool non riescono davvero a darti quello che ti serve per prendere una decisione per le prestazioni. Ma quando li paragoni, i thread vincono e in particolare un pool di thread. Sono distribuiti i migliori tra le CPU e si avviano più velocemente.

Quello che dovrebbe essere discusso è il fatto che l’unità principale di esecuzione di Windows (incluso Windows 10) è un thread e il sovraccarico del contesto del SO è solitamente trascurabile. In poche parole, non sono stato in grado di trovare prove convincenti di molti di questi articoli, indipendentemente dal fatto che l’articolo rivendichi prestazioni più elevate risparmiando il cambio di contesto o un migliore utilizzo della CPU.

Ora per un po ‘di realismo:

La maggior parte di noi non ha bisogno che la nostra applicazione sia deterministica, e molti di noi non hanno uno sfondo difficile con i thread, che ad esempio spesso viene fornito con lo sviluppo di un sistema operativo. Quello che ho scritto sopra non è per un principiante.

Quindi quello che potrebbe essere più importante è discutere è ciò che è facile da programmare.

Se crei il tuo pool di thread, devi scrivere un po ‘di cose da fare in quanto dovrai occuparti dello stato dell’esecuzione del monitoraggio, di come simulare la sospensione e il ripristino e di come annullare l’esecuzione, anche in un’applicazione. spegnimento. Potrebbe anche essere necessario preoccuparsi se si desidera espandere in modo dinamico il pool e anche quale limite di capacità avrà il pool. Posso scrivere una tale struttura in un’ora, ma è perché l’ho fatto così tante volte.

Forse il modo più semplice per scrivere un’unità di esecuzione è usare un compito. La bellezza di un compito è che puoi crearne uno e metterlo in linea nel tuo codice (anche se la prudenza potrebbe essere giustificata). È ansible passare un token di cancellazione da gestire quando si desidera annullare l’attività. Inoltre, usa l’approccio promettente per concatenare gli eventi e puoi farli restituire un tipo specifico di valore. Inoltre, con asincrono e attendi, esistono più opzioni e il tuo codice sarà più portabile.

In sostanza, è importante comprendere i pro e i contro con Tasks vs. Threads rispetto a .NET ThreadPool. Se ho bisogno di prestazioni elevate, userò i thread e preferisco usare la mia piscina.

Un modo semplice per confrontare è avviare 512 discussioni, 512 attività e 512 discussioni ThreadPool. Troverai un ritardo all’inizio con Thread (quindi, perché scrivere un pool di thread), ma tutti i thread 512 verranno eseguiti in pochi secondi mentre i thread Task e .NET ThreadPool impiegano fino a pochi minuti per l’avvio.

Di seguito sono riportati i risultati di tale test (i5 quad core con 16 GB di RAM), dando ogni 30 secondi per l’esecuzione. Il codice eseguito esegue semplici I / O su un disco SSD.

Risultati del test

I pool di thread sono grandi quando si hanno più attività da elaborare rispetto ai thread disponibili.

È ansible aggiungere tutte le attività a un pool di thread e specificare il numero massimo di thread che possono essere eseguiti in un determinato momento.

Dai un’occhiata a questa pagina su MSDN: http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx

Usa sempre un pool di thread, se puoi, lavora al massimo livello di astrazione ansible. I pool di thread nascondono la creazione e la distruzione di thread per te, di solito è una buona cosa!

La maggior parte delle volte è ansible utilizzare il pool mentre si evita il costoso processo di creazione del thread.

Tuttavia in alcuni scenari potresti voler creare un thread. Ad esempio, se non si è l’unico che utilizza il pool di thread e il thread che si crea è di lunga durata (per evitare di consumare risorse condivise) o, ad esempio, se si desidera controllare lo stacking del thread.

Se hai un’attività in background che durerà a lungo, come per tutta la durata della tua applicazione, allora creare una tua discussione è una cosa ragionevole. Se si hanno lavori brevi che devono essere eseguiti in un thread, utilizzare il pool di thread.

In un’applicazione in cui si creano molti thread, l’overhead della creazione dei thread diventa sostanziale. L’utilizzo del pool di thread crea i thread una volta e li riutilizza, evitando così l’overhead di creazione del thread.

In un’applicazione a cui ho lavorato, il passaggio dalla creazione di thread all’utilizzo del pool di thread per i thread di breve durata ha davvero aiutato la messa a punto dell’applicazione.

Non dimenticare di investigare sul lavoratore in background.

Trovo che per molte situazioni, mi dà proprio quello che voglio senza il sollevamento pesante.

Saluti.

Di solito uso Threadpool ogni volta che ho bisogno di fare qualcosa su un altro thread e non mi interessa davvero quando corre o finisce. Qualcosa come la registrazione o forse anche lo sfondo di scaricare un file (anche se ci sono modi migliori per farlo in stile asincrono). Uso il mio thread quando ho bisogno di più controllo. Anche quello che ho trovato è usare una coda di Threadsafe (hackerare la tua) per memorizzare “oggetti di comando” è bello quando ho più comandi su cui ho bisogno di lavorare in> 1 thread. Quindi potresti dividere un file Xml e mettere ogni elemento in una coda e poi avere più thread che lavorano su alcuni processi su questi elementi. Ho scritto una tale coda di ritorno in uni (VB.net!) Che ho convertito in C #. L’ho incluso di seguito senza alcun motivo particolare (questo codice potrebbe contenere alcuni errori).

 using System.Collections.Generic; using System.Threading; namespace ThreadSafeQueue { public class ThreadSafeQueue { private Queue _queue; public ThreadSafeQueue() { _queue = new Queue(); } public void EnqueueSafe(T item) { lock ( this ) { _queue.Enqueue(item); if ( _queue.Count >= 1 ) Monitor.Pulse(this); } } public T DequeueSafe() { lock ( this ) { while ( _queue.Count <= 0 ) Monitor.Wait(this); return this.DeEnqueueUnblock(); } } private T DeEnqueueUnblock() { return _queue.Dequeue(); } } } 

Volevo un pool di thread per distribuire il lavoro tra i core con la minore latenza ansible e che non doveva giocare bene con altre applicazioni. Ho scoperto che le prestazioni del pool di thread .NET non erano buone come potevano essere. Sapevo di volere un thread per core, così ho scritto la mia class sostitutiva del pool di thread. Il codice viene fornito come risposta a un’altra domanda StackOverflow qui .

Per quanto riguarda la domanda originale, il pool di thread è utile per interrompere i calcoli ripetitivi in ​​parti che possono essere eseguite in parallelo (supponendo che possano essere eseguite in parallelo senza modificare il risultato). La gestione manuale dei thread è utile per attività come UI e IO.