Quando utilizzare il pool di thread in C #? [chiuso]


127

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

Risposte:


47

Se hai molte attività logiche che richiedono un'elaborazione costante e desideri che ciò avvenga in parallelo, utilizza il pool + scheduler.

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

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

Normalmente un pool è composto da 2 thread per processore (quindi probabilmente 4 al giorno d'oggi), tuttavia è possibile impostare la quantità di thread che si desidera, se si conosce il numero di thread necessari.

Modifica: la ragione per creare i tuoi thread è a causa dei cambiamenti di contesto (questo è quando i thread devono scambiarsi dentro e fuori dal processo, insieme alla loro memoria). Avere cambiamenti di contesto inutili, ad esempio quando non si usano i thread, lasciandoli semplicemente seduti come si potrebbe dire, può facilmente dimezzare le prestazioni del programma (supponiamo che tu abbia 3 thread inattivi 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


2
Ok, ma puoi spiegare perché questo è il modo in cui lo approcci? Ad esempio, qual è il rovescio della medaglia nell'utilizzare il pool di thread per scaricare da server remoti o fare IO sul disco?

8
Se un thread è in attesa su un oggetto di sincronizzazione (evento, semaforo, mutex, ecc.), Il thread non consuma CPU.
Brannon,

7
Come ha detto Brannon, un mito comune è che la creazione di più thread influisce sulle prestazioni. In realtà, i thread non utilizzati consumano pochissime risorse. Gli switch di contesto iniziano a rappresentare un problema solo nei server a richiesta molto elevata (in questo caso, vedere le porte di completamento I / O per un'alternativa).
FDCastel,

12
I thread inattivi influiscono sulle prestazioni? Dipende da come aspettano. Se ben scritti e in attesa su un oggetto di sincronizzazione, non dovrebbero consumare risorse CPU. Se stai aspettando in un ciclo che si sveglia periodicamente per controllare i risultati, allora sta sprecando CPU. Come sempre si tratta di una buona codifica.
Bill

2
I thread gestiti inattivi consumano memoria per il loro stack. L'impostazione predefinita è 1 MiB per thread. Quindi è meglio che tutti i thread funzionino.
Vadym Stetsiak,

48

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

Quando vuoi limitare il numero di thread in esecuzione o non vuoi il sovraccarico di crearli e distruggerli, usa un pool di thread.

Per piccole attività, il libro che leggi significa attività con una breve durata. Se ci vogliono dieci secondi per creare un thread che gira solo per un secondo, quello è un posto dove dovresti usare i pool (ignora le mie cifre reali, è il rapporto che conta).

Altrimenti trascorri gran parte del tuo tempo a creare e distruggere discussioni piuttosto che semplicemente fare il lavoro che intendono fare.


28

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

Il post ha anche alcuni punti su quando non dovresti usare il pool di thread e iniziare invece il tuo thread.


8
-1 per il collegamento. Sono sicuro che sia un buon collegamento, ma mi aspetto che SO sia autosufficiente.
Jon Davis,

26
@ stimpy77 - allora è un'aspettativa sbagliata. La SO non può mai essere autosufficiente, perché non è né l'autorità suprema su tutte le domande, né tutte le informazioni approfondite su ciascun argomento possono (e dovrebbero) essere duplicate in ogni risposta SO che tocca quell'argomento. (e non credo che tu abbia nemmeno abbastanza reputazione da sottovalutare ogni singola risposta di Jon Skeet da solo che ha un link in uscita, figuriamoci tutte le risposte di tutti gli utenti SO che hanno link in uscita :-))
Franci Penov

2
Forse ero troppo conciso, forse avrei dovuto chiarire. Non sono contrario ai link. Sono contrario alle risposte che contengono solo un link. Non credo sia una risposta. Ora, se fosse stato pubblicato un breve riassunto della risposta per riassumere come si applica il contenuto collegato, sarebbe accettabile. Inoltre, sono venuto qui in cerca di una risposta allo stesso problema e questa risposta mi ha irritato perché era ancora un altro link su cui ho dovuto fare clic per avere idea di cosa potesse dire in riferimento al problema specifico. Ad ogni modo, a che cosa si riferisce Jon Skeet? E perché dovrei preoccuparmi?
Jon Davis,

8
"Sei arrivato a questo post due anni dopo che è stato pubblicato e tutto ciò che ho copiato qui potrebbe essere ormai obsoleto." Quindi potrebbe un link. Pubblica un riassunto breve ma completo quando pubblichi un link, non sai mai se un link diventa stantio o morto.
Jon Davis,

2
Non sono d'accordo con la debolezza: non l'idea di post contenenti tonnellate di informazioni a causa di fattibilità, né di chiamare qualcuno per questo. Direi che è più probabile che un link diventi inutilizzabile rispetto al contenuto che diventa deprecato / ovviato. Quindi, più contenuti è bello quando l'occasione lo consente. Siamo tutti (principalmente) volontari, quindi sii grato per quello che ottieni - grazie Franci :)
zanlok,

14

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

Leggi almeno la sezione "Introduzione". L'e-book fornisce una grande introduzione e include anche una vasta gamma di informazioni di threading avanzate.

Sapere se utilizzare o meno il pool di thread è solo l'inizio. Successivamente dovrai determinare quale metodo di immissione del pool di thread soddisfa meglio le tue esigenze:

  • Task Libreria parallela (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • Delegati asincroni
  • BackgroundWorker

Questo e-book spiega tutto questo e consiglia quando usarli anziché creare il tuo thread.


8

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

Ora, se ciascuno di questi componenti mettesse in coda gli elementi nel pool di thread, si otterrebbe un sovraccarico di commutazione del contesto molto inferiore.

Il pool di thread è progettato per massimizzare il lavoro svolto sulle CPU (o sui core della CPU). Ecco perché, per impostazione predefinita, il pool di thread esegue più thread per processore.

Vi sono alcune situazioni in cui non si desidera utilizzare il pool di thread. Se stai aspettando su I / O, o stai aspettando un evento, ecc. Allora leghi quel thread del pool di thread e non può essere utilizzato da nessun altro. La stessa idea si applica alle attività di lunga durata, sebbene ciò che costituisce un'attività di lunga durata sia soggettivo.

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

Nota: è stato chiesto di utilizzare un thread del pool di thread per scaricare dati o eseguire l'I / O su disco. Per questo non dovresti usare un thread di pool di thread (per i motivi che ho descritto sopra). Utilizzare invece l'I / O asincrono (ovvero i metodi BeginXX ed EndXX). Per un FileStreamche sarebbe BeginReade EndRead. Per un HttpWebRequestche sarebbe BeginGetResponsee EndGetResponse. Sono più complicati da usare, ma sono il modo corretto di eseguire I / O multi-thread.


1
ThreadPool è un'automazione intelligente. "Se la sua coda rimane ferma per più di mezzo secondo, risponde creando più thread - uno ogni mezzo secondo - fino alla capacità del pool di thread" ( albahari.com/threading/#_Optimizing_the_Thread_Pool ). Anche le operazioni quasi asincrone con BeginXXX-EndXXX vengono utilizzate tramite ThreadPool. Quindi è normale usare ThreadPool per scaricare dati e spesso implicitamente usati.
Artru,

6

Fai attenzione al pool di thread .NET per le operazioni che potrebbero bloccare qualsiasi parte significativa, variabile o sconosciuta della loro elaborazione, poiché è soggetto alla fame di thread. Prendi in considerazione l'utilizzo delle estensioni parallele .NET, che forniscono un buon numero di astrazioni logiche rispetto alle operazioni thread. Includono anche un nuovo scheduler, che dovrebbe essere un miglioramento su ThreadPool. Vedi qui


2
L'abbiamo scoperto nel modo più duro! ASP.Net utilizza il threadpool appare e quindi non potremmo usarlo così aggressivo come vorremmo.
noocyte,

3

Un motivo per utilizzare il pool di thread solo per piccole attività è che esiste un numero limitato di thread del pool di thread. Se ne viene utilizzato uno per un lungo periodo, si interrompe l'utilizzo del thread da parte di altri codici. Se ciò accade più volte, il pool di thread può esaurirsi.

L'uso del pool di thread può avere effetti discreti: alcuni timer .NET utilizzano thread del pool di thread e non si attivano, ad esempio.


2

Se hai un'attività in background che durerà a lungo, come per tutta la durata della tua applicazione, quindi creare il tuo thread è 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'uso del pool di thread crea i thread una volta e li riutilizza, evitando così l'overhead di creazione dei thread.

In un'applicazione su cui ho lavorato, il passaggio dalla creazione di thread all'utilizzo del pool di thread per i thread di breve durata ha davvero aiutato il passaggio dell'applicazione.


Si prega di chiarire se si intende "un pool di thread" o "il pool di thread". Queste sono cose molto diverse (almeno nel MS CLR).
bzlm,

2

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

Così tanti articoli su Task vs. Threads vs. .NET ThreadPool non riescono a darti davvero ciò di cui hai bisogno per prendere una decisione in merito alle prestazioni. Ma quando li confronti, i thread vincono e soprattutto un pool di thread. Sono distribuiti al meglio tra le CPU e si avviano più velocemente.

Ciò che dovrebbe essere discusso è il fatto che l'unità di esecuzione principale di Windows (incluso Windows 10) è un thread e l'overhead di commutazione del contesto del sistema operativo è generalmente trascurabile. In poche parole, non sono stato in grado di trovare prove convincenti di molti di questi articoli, sia che l'articolo rivendichi prestazioni più elevate salvando il cambio di contesto o un migliore utilizzo della CPU.

Ora per un po 'di realismo:

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

Quindi ciò che può essere più importante è discutere di ciò che è facile da programmare.

Se crei il tuo pool di thread, avrai un po 'di scrittura da fare in quanto dovrai preoccuparti del monitoraggio dello stato di esecuzione, di come simulare la sospensione e il ripristino e di come annullare l'esecuzione, anche in un'applicazione spegnimento. Potresti anche doverti preoccupare se vuoi far crescere in modo dinamico il tuo pool e anche quali limiti di capacità avrà il tuo pool. Posso scrivere un tale framework in un'ora, ma è perché l'ho fatto così tante volte.

Forse il modo più semplice per scrivere un'unità di esecuzione è usare un'attività. Il bello di un compito è che puoi crearne uno e dargli il via in linea nel tuo codice (anche se la cautela può essere giustificata). È possibile passare un token di annullamento da gestire quando si desidera annullare l'attività. Inoltre, utilizza l'approccio promettente per concatenare gli eventi e puoi 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 ei contro con Task vs. Threads vs. .NET ThreadPool. Se ho bisogno di prestazioni elevate, userò i thread e preferisco usare il mio pool.

Un modo semplice per confrontare è l'avvio di 512 thread, 512 attività e 512 thread ThreadPool. All'inizio troverai un ritardo con i thread (quindi, perché scrivere un pool di thread), ma tutti i 512 thread verranno eseguiti in pochi secondi mentre le attività e i thread .NET ThreadPool impiegano fino a pochi minuti per iniziare.

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

Risultati del test


1
Cordiali saluti, ho dimenticato di menzionare che le attività e i thread .NET sono una concorrenza simulata all'interno di .NET e con l'esecuzione della gestione all'interno di .NET non il sistema operativo, quest'ultimo essendo molto più efficiente nella gestione delle esecuzioni simultanee. Uso molte attività ma utilizzo un thread del sistema operativo per prestazioni di esecuzione pesanti. MS afferma che le attività e i thread .NET sono migliori, ma sono in generale per bilanciare la concorrenza tra le app .NET. Un'app server tuttavia avrebbe le migliori prestazioni consentendo al sistema operativo di gestire la concorrenza.

Mi piacerebbe vedere l'implementazione del tuo Threadpool personalizzato. Bello scrivere!
Francis,

Non capisco i risultati del test. Cosa significa "Unità funzionanti"? Confronti 34 taks con 512 thread? Potresti spiegarlo per favore?
Elmue,

L'unità è solo un metodo per eseguire contemporaneamente in un thread di lavoro Task, Thread o .NET ThreadPool, il mio test che confronta le prestazioni di avvio / esecuzione. Ogni test ha 30 secondi per generare 512 thread da zero, 512 attività, 512 thread di lavoro ThreadPool o riprendere un pool di 512 thread avviati in attesa di un contesto da eseguire. I thread di lavoro di Tasks e ThreadPool hanno una rotazione lenta, quindi 30 secondi non sono abbastanza tempo per girarli tutti. Tuttavia, se il conteggio dei thread di lavoro min di ThreadPool è impostato per la prima volta su 512, sia i thread di attività sia i thread di lavoro di ThreadPool gireranno quasi alla stessa velocità di 512 thread da zero.


1

I pool di thread sono eccezionali quando hai più attività da elaborare rispetto ai thread disponibili.

È possibile 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


Ok, immagino che questo sia legato alla mia altra domanda. Come fai a sapere quante discussioni disponibili hai in un dato momento?

Beh, è ​​difficile da dire. Dovrai eseguire test delle prestazioni. Dopo un certo punto l'aggiunta di più thread non ti darà più velocità. Scopri quanti processori sono presenti sulla macchina, sarà un buon punto di partenza. Quindi sali da lì, se la velocità di elaborazione non migliora, non aggiungere altri thread.
lajos,

1

Se possibile, usa sempre un pool di thread, lavora al massimo livello di astrazione possibile. I pool di thread nascondono la creazione e la distruzione di thread per te, questa è generalmente una buona cosa!


1

Il più delle volte è possibile utilizzare il pool evitando il costoso processo di creazione del thread.

Tuttavia, in alcuni scenari potresti voler creare un thread. Ad esempio, se non sei l'unico a utilizzare il pool di thread e il thread che crei ha una lunga durata (per evitare di consumare risorse condivise) o, ad esempio, se vuoi controllare lo stack delle dimensioni del thread.


1

Non dimenticare di indagare sul lavoratore in background.

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

Saluti.


quando è un'app semplice che rimane in esecuzione e hai un'altra attività da svolgere, è molto facile eseguire questo codice. tuttavia non hai fornito link: specifiche e tutorial
zanlok,

0

Di solito uso Threadpool ogni volta che devo semplicemente fare qualcosa su un altro thread e non mi interessa davvero quando funziona o finisce. Qualcosa come la registrazione o forse anche il download in background di un file (anche se ci sono modi migliori per farlo in stile asincrono). Uso il mio thread quando ho bisogno di più controllo. Inoltre quello che ho trovato è usare una coda Threadsafe (hackerare la tua) per memorizzare "oggetti 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 quindi avere più thread che lavorano per fare un po 'di elaborazione su questi elementi. Ho scritto una coda del genere in uni (VB.net!) Che ho convertito in C #. L'ho incluso di seguito per nessun motivo particolare (questo codice potrebbe contenere alcuni errori).

using System.Collections.Generic;
using System.Threading;

namespace ThreadSafeQueue {
    public class ThreadSafeQueue<T> {
        private Queue<T> _queue;

        public ThreadSafeQueue() {
            _queue = new Queue<T>();
        }

        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();
        }
    }
}

Alcuni problemi con questo approccio: - Le chiamate a DequeueSafe () attenderanno fino a quando un elemento non sarà EnqueuedSafe (). Prendi in considerazione l'utilizzo di uno dei sovraccarichi Monitor.Wait () specificando un timeout. - Il blocco su questo non è secondo le migliori pratiche, piuttosto crea un campo oggetto di sola lettura. - Anche se Monitor.Pulse () è leggero, chiamarlo quando la coda contiene solo 1 elemento sarebbe più efficiente. - DeEnqueueUnblock () dovrebbe preferibilmente controllare la coda.Conteggio> 0. (necessario se si utilizzano Monitor.PulseAll o timeout di attesa)
Craig Nicholson,

0

Volevo che un pool di thread distribuisse il lavoro tra i core con la minor latenza possibile e che non doveva funzionare 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, quindi ho scritto la mia classe 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 suddividere 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 l'interfaccia utente e IO.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.