Domanda

Ho cercato di imparare la programmazione multi-thread in C # e sono confuso su quando è meglio usare un pool di thread invece di 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?

È stato utile?

Soluzione

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

Se devi eseguire contemporaneamente le tue attività relative all'IO, come il download di cose da server remoti o l'accesso al disco, ma devi dirlo una volta ogni pochi minuti, quindi crea i tuoi thread e uccidili una volta terminato.

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 puoi impostare la quantità di thread che desideri, se sai quanti ne hai bisogno.

Modifica: il motivo 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

Altri suggerimenti

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 funziona 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 i thread piuttosto che semplicemente a fare il lavoro che devono fare.

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.

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

Almeno leggi " Guida introduttiva " sezione. L'e-book offre una grande introduzione e include anche una vasta gamma di informazioni di threading avanzate.

Sapere se usare o meno il pool di thread è solo l'inizio. Successivamente dovrai determinare quale metodo di immissione del pool di thread si adatta meglio alle tue esigenze:

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

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

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 quei componenti accodasse gli elementi al pool di thread, si otterrebbe un sovraccarico di cambio di contesto molto meno.

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.

Anche Pax Diablo fa 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: hai 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 del pool di thread (per i motivi che ho descritto sopra). Utilizzare invece l'I / O asincrono (ovvero i metodi BeginXX ed EndXX). Per un FileStream che sarebbe BeginRead e EndRead . Per un HttpWebRequest che sarebbe BeginGetResponse e EndGetResponse . Sono più complicati da usare, ma sono il modo corretto di eseguire I / O multi-thread.

Prestare 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

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 sottili: alcuni timer .NET utilizzano thread del pool di thread e non si attivano, ad esempio.

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

In un'applicazione in cui si stanno creando 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 a superare l'applicazione.

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 di un contesto da eseguire (un oggetto con uno standard interfaccia implementata dal 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 è ciò che è facile da programmare.

Se crei il tuo pool di thread, avrai un po 'di scrittura da fare mentre dovrai occuparti del monitoraggio dello stato di esecuzione, di come simulare la sospensione e riprendere e di come annullare l'esecuzione & # 8211; incluso in un arresto a livello di applicazione. Potrebbe anche essere necessario preoccuparsi se si desidera aumentare in modo dinamico il pool e anche quale limitazione di capacità avrà il 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 asincronizzazione e attesa, 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. Troverai un ritardo all'inizio con i thread (quindi, perché scrivere un pool di thread), ma tutti i 512 thread verranno eseguiti in pochi secondi mentre i thread Task e .NET ThreadPool impiegano fino a pochi minuti per iniziare tutti .

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 dei test

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

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

Scopri questa su MSDN : http://msdn.microsoft.com/en-us /library/3dasc8as(VS.80).aspx

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!

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 è di lunga durata (per evitare di consumare risorse condivise) o, ad esempio, se vuoi controllare lo stack delle dimensioni del thread.

Non dimenticare di indagare sul lavoratore in background.

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

Saluti.

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 archiviare " oggetti di comando " è bello quando ho più comandi su cui devo 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();
        }
    }
}

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top