Quanti thread sono troppi?


312

Sto scrivendo un server e invio ciascuna azione in un thread separato quando viene ricevuta la richiesta. Lo faccio perché quasi ogni richiesta fa una query al database. Sto usando una libreria di threadpool per ridurre la costruzione / distruzione dei thread.

La mia domanda è: qual è un buon punto di interruzione per thread I / O come questi? So che sarebbe solo una stima approssimativa, ma ne stiamo parlando a centinaia? Migliaia?

Come farei per capire quale sarebbe questo taglio?


MODIFICARE:

Grazie a tutti per le vostre risposte, sembra che dovrò solo provarlo per scoprire il mio limite di conteggio dei thread. La domanda è però: come faccio a sapere di aver raggiunto quel limite? Cosa devo misurare esattamente?


1
@ryeguy: L'intero punto qui è che non dovresti impostare alcun massimo nel threadpool se non ci sono problemi di prestazioni con cui iniziare. La maggior parte dei consigli di limitare un thread pool a ~ 100 thread è ridicola, la maggior parte dei pool di thread ha / way / più thread di così e non ha mai problemi.
GEOCHET,

ryeguy, vedi oltre alla mia risposta qui sotto riguardo a cosa misurare.
paxdiablo,

Non dimenticare che Python è per sua natura, non molto adatto ai multi-thread. In qualsiasi momento, viene eseguito un singolo codice operativo bytecode. Questo perché Python utilizza Global Interpreter Lock.
Chiedere

1
@Jay D: Direi che il momento in cui hai toccato il soffitto è quando la tua performance inizia a calare.
ninjalj,

6
@GEOCHET "Il punto qui è che non dovresti impostare il massimo nel threadpool" Ummm ... dire cosa? I pool di thread di dimensioni fisse presentano i vantaggi di un gradevole degrado e scalabilità. Ad esempio in un'impostazione di rete, se stai generando nuovi thread basati su connessioni client, senza una dimensione fissa del pool corri il vero pericolo di apprendimento ( nel modo più duro ) quanti thread può gestire il tuo server e ogni singolo client connesso soffrirà. Un pool di dimensioni fisse si comporta come una valvola a tubo impedendo al server di provare a mordere più di quanto possa masticare.
b1nary.atr0phy,

Risposte:


206

Alcuni direbbero che due thread sono troppi: non sono proprio in quel campo :-)

Ecco il mio consiglio: misura, non indovinare. Un suggerimento è di renderlo configurabile e inizialmente impostarlo su 100, quindi rilasciare il software allo stato brado e monitorare ciò che accade.

Se l'utilizzo del thread raggiunge il picco a 3, 100 è troppo. Se rimane a 100 per la maggior parte della giornata, aumentalo fino a 200 e vedi cosa succede.

Si potrebbe in realtà avere il codice stesso monitorare l'utilizzo e regolare la configurazione per la prossima volta che si avvia ma questo è probabilmente eccessivo.


Per chiarimenti ed elaborazione:

Non sto proponendo di far rotolare il tuo sottosistema di pool di thread, usa sicuramente quello che hai. Ma, dal momento che stavi chiedendo un buon punto di interruzione per i thread, suppongo che l'implementazione del pool di thread abbia la capacità di limitare il numero massimo di thread creati (che è una buona cosa).

Ho scritto il codice di pooling delle connessioni di thread e database e hanno le seguenti funzionalità (che credo siano essenziali per le prestazioni):

  • un numero minimo di thread attivi.
  • un numero massimo di thread.
  • chiusura di thread non utilizzati da un po 'di tempo.

Il primo imposta una linea di base per le prestazioni minime in termini di client del pool di thread (questo numero di thread è sempre disponibile per l'uso). Il secondo imposta una restrizione sull'utilizzo delle risorse da parte dei thread attivi. Il terzo ti riporta alla baseline in periodi di silenzio in modo da ridurre al minimo l'uso delle risorse.

È necessario bilanciare l'utilizzo delle risorse di thread non utilizzati (A) rispetto all'utilizzo delle risorse di non disporre di thread sufficienti per eseguire il lavoro (B).

(A) è generalmente l'utilizzo della memoria (stack e così via) poiché un thread che non fa alcun lavoro non utilizzerà gran parte della CPU. (B) sarà generalmente un ritardo nell'elaborazione delle richieste quando arrivano poiché è necessario attendere che una discussione diventi disponibile.

Ecco perché misuri. Come dici, la stragrande maggioranza dei tuoi thread aspetterà una risposta dal database in modo che non siano in esecuzione. Ci sono due fattori che influenzano il numero di thread che dovresti consentire.

Il primo è il numero di connessioni DB disponibili. Questo potrebbe essere un limite rigido a meno che tu non possa aumentarlo nel DBMS - suppongo che il tuo DBMS possa prendere un numero illimitato di connessioni in questo caso (anche se idealmente dovresti anche misurarlo).

Quindi, il numero di thread che dovresti avere dipende dal tuo uso storico. Il minimo che dovresti avere è il numero minimo che tu abbia mai avuto in esecuzione + A%, con un minimo assoluto di (ad esempio, e rendilo configurabile proprio come A) 5.

Il numero massimo di thread dovrebbe essere il massimo storico + B%.

Dovresti anche monitorare i cambiamenti di comportamento. Se, per qualche motivo, l'utilizzo va al 100% di quello disponibile per un periodo di tempo significativo (in modo da influire sulle prestazioni dei clienti), è necessario aumentare il massimo consentito fino a quando non sarà nuovamente superiore del B%.


In risposta a "cosa devo misurare esattamente?" domanda:

Quello che dovresti misurare specificamente è la quantità massima di thread in uso simultaneo (ad esempio, in attesa di un ritorno dalla chiamata DB) sotto carico. Quindi aggiungi un fattore di sicurezza del 10% per esempio (sottolineato, poiché altri poster sembrano prendere i miei esempi come raccomandazioni fisse).

Inoltre, ciò dovrebbe essere fatto nell'ambiente di produzione per l'ottimizzazione. Va bene ottenere un preventivo in anticipo, ma non si sa mai quale produzione si farà strada (motivo per cui tutte queste cose dovrebbero essere configurabili in fase di esecuzione). Questo per cogliere una situazione come il raddoppio imprevisto delle chiamate client in arrivo.


Se i thread vengono generati su richieste in arrivo, l'utilizzo dei thread rispecchierà il numero di richieste non verificate. Non c'è modo di determinare il numero "ottimale" da questo. In effetti, troverai più thread che causano più contese di risorse e quindi il numero di thread attivi aumenterà.
Andrew Grant,

@Andrew, la creazione del thread richiede tempo e puoi determinare il numero ottimale in base ai dati storici [+ N%] (quindi misura, non indovinare). Inoltre, più thread causano la contesa di risorse solo quando stanno lavorando, senza attendere un segnale / semaforo.
paxdiablo,

Dove sono questi dati sulla "creazione di thread" che causano un problema di prestazioni quando si utilizza un pool di thread? Un buon pool di thread non creerebbe e distruggerà thread tra attività.
GEOCHET,

@Pax Se tutti i thread sono in attesa sugli stessi semafori per eseguire query DB, questa è la definizione stessa di contesa. Inoltre, non è vero dire che i thread non costano nulla se stanno aspettando un semaforo.
Andrew Grant,

1
@Andrew, non riesco a capire perché dovresti bloccare semaforo le query DB, qualsiasi DB decente consentirà l'accesso simultaneo, con molti thread in attesa delle risposte. E i thread non dovrebbero costare alcun tempo di esecuzione mentre i semafori sono bloccati, dovrebbero rimanere nella coda bloccata fino al rilascio del semaforo.
paxdiablo,

36

Questa domanda è stata discussa abbastanza a fondo e non ho avuto la possibilità di leggere tutte le risposte. Ma ecco alcune cose da prendere in considerazione osservando il limite superiore del numero di thread simultanei che possono coesistere pacificamente in un determinato sistema.

  1. Dimensioni stack thread: in Linux la dimensione dello stack thread predefinita è 8 MB (è possibile utilizzare ulimit -a per scoprirlo).
  2. Max Memoria virtuale supportata da una determinata variante del sistema operativo. Linux Kernel 2.4 supporta uno spazio di indirizzi di memoria di 2 GB. con Kernel 2.6, I un po 'più grande (3 GB)
  3. [1] mostra i calcoli per il numero massimo di thread per una determinata VM massima supportata. Per 2.4 risulta essere di circa 255 thread. per 2.6 il numero è un po 'più grande.
  4. Che tipo di programmatore del kernel hai. Confrontando lo scheduler del kernel Linux 2.4 con 2.6, il successivo ti dà una pianificazione O (1) senza dipendenza dal numero di compiti esistenti in un sistema mentre il primo è più di un O (n). Quindi anche le capacità SMP del programma del kernel svolgono un buon ruolo nel numero massimo di thread sostenibili in un sistema.

Ora è possibile ottimizzare le dimensioni dello stack per incorporare più thread ma è necessario tenere conto delle spese generali di gestione dei thread (creazione / distruzione e pianificazione). È possibile imporre l'affinità della CPU a un determinato processo, nonché a un determinato thread per legarli a CPU specifiche per evitare spese generali di migrazione dei thread tra le CPU ed evitare problemi di liquidità.

Si noti che si possono creare migliaia di thread a suo piacimento, ma quando Linux esaurisce la VM, inizia casualmente a uccidere i processi (quindi i thread). Questo per evitare che il profilo di utilità venga massimizzato. (La funzione utility indica l'utilità a livello di sistema per una determinata quantità di risorse. Con risorse costanti in questo caso Cicli e memoria della CPU, la curva dell'utilità si appiattisce con un numero sempre maggiore di attività).

Sono sicuro che anche lo scheduler del kernel di Windows fa qualcosa del genere per gestire un utilizzo eccessivo delle risorse

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/


17

Se i tuoi thread eseguono qualsiasi tipo di lavoro ad alta intensità di risorse (CPU / disco), raramente vedrai vantaggi oltre uno o due e troppi uccideranno le prestazioni molto rapidamente.

Il "caso migliore" è che i thread successivi si bloccheranno mentre i primi sono completi, oppure alcuni avranno blocchi a basso costo sulle risorse con bassa contesa. La cosa peggiore è che inizi a bloccare la cache / disco / rete e il tuo throughput complessivo scende a terra.

Una buona soluzione è quella di inserire richieste in un pool che vengono poi inviate ai thread di lavoro da un pool di thread (e sì, evitare la creazione / distruzione di thread continui è un ottimo primo passo).

Il numero di thread attivi in ​​questo pool può quindi essere modificato e ridimensionato in base ai risultati della profilazione, dell'hardware su cui si sta eseguendo e di altre cose che potrebbero verificarsi sulla macchina.


Sì, e dovrebbe essere usato in combinazione con una coda o un pool di richieste.
Andrew Grant,

2
@Andrew: Perché? Dovrebbe aggiungere un'attività al pool di thread ogni volta che riceve una richiesta. Spetta al pool di thread allocare un thread per l'attività quando ce n'è uno disponibile.
GEOCHET,

Quindi cosa fai quando hai centinaia di richieste in arrivo e senza thread? Crea di più? Bloccare? Restituire un errore? Posizionare le richieste in un pool che può essere grande quanto necessario e quindi inviare queste richieste in coda al pool di thread man mano che i thread diventano liberi.
Andrew Grant,

"un numero di thread viene creato per eseguire una serie di attività, che di solito sono organizzate in una coda. In genere, ci sono molte più attività rispetto ai thread. Non appena un thread completa la sua attività, richiederà l'attività successiva dalla coda fino al completamento di tutte le attività. "
GEOCHET,

@Andrew: Non sono sicuro di quello che python pool di thread l'OP sta usando, ma se si vuole un esempio reale mondo di questa funzionalità che sto descrivendo: msdn.microsoft.com/en-us/library/...
GEOCHET

10

Una cosa da tenere a mente è che python (almeno la versione basata su C) utilizza quello che viene chiamato un blocco dell'interprete globale che può avere un impatto enorme sulle prestazioni su macchine multi-core.

Se hai davvero bisogno del massimo da Python multithread, potresti prendere in considerazione l'uso di Jython o qualcosa del genere.


4
Dopo aver letto questo, ho provato a eseguire il setaccio delle attività di Eratosthenes su tre thread. Abbastanza sicuro, in realtà era più lento del 50% rispetto all'esecuzione delle stesse attività in un singolo thread. Grazie per il testa a testa. Stavo eseguendo Eclipse Pydev su una macchina virtuale a cui erano state assegnate due CPU. Successivamente, proverò uno scenario che prevede alcune chiamate al database.
Don Kirkby,

3
Esistono due (almeno) tipi di task: associato alla CPU (ad es. Elaborazione delle immagini) e associato I / O (ad es. Download dalla rete). Ovviamente, il "problema" di GIL non influirà troppo sulle attività associate agli I / O. Se le tue attività sono legate alla CPU, dovresti considerare il multiprocessing invece del multithreading.
iutinvg,

1
sì, il thread di Python è migliorato se hai un sacco di rete io.Io lo cambio in thread e ho ottenuto 10 * più veloce del normale codice ...
tyan

8

Come giustamente ha detto Pax, misura, non indovinare . Ciò che ho fatto per DNSwitness e i risultati è stato sorprendente: il numero ideale di thread era molto più alto di quanto pensassi, qualcosa come 15.000 thread per ottenere i risultati più veloci.

Certo, dipende da molte cose, ecco perché devi misurarti.

Misure complete (solo in francese) nel Combien de fils d'exécution? .


1
15.000? È un po 'più alto di quanto mi sarei aspettato. Tuttavia, se è quello che hai, allora è quello che hai, non posso discuterne.
paxdiablo,

2
Per questa specifica applicazione, la maggior parte dei thread attende solo una risposta dal server DNS. Quindi, più parallelismo, meglio è, nel tempo dell'orologio da parete.
Bortzmeyer,

18
Penso che se si hanno quei 15000 thread che si bloccano su alcuni I / O esterni, una soluzione migliore sarebbe massicciamente meno thread ma con un modello asincrono. Parlo per esperienza qui.
Steve,

5

Ho scritto diverse app multi-thread. In genere, consento al numero di potenziali thread di essere specificato da un file di configurazione. Quando mi sono sintonizzato per clienti specifici, ho impostato il numero abbastanza alto che il mio utilizzo di tutti i core della CPU era piuttosto alto, ma non così alto da incorrere in problemi di memoria (questi erano sistemi operativi a 32 bit sul tempo).

In altre parole, una volta raggiunto il collo di bottiglia, che si tratti di CPU, throughput del database, throughput del disco, ecc., L'aggiunta di più thread non aumenterà le prestazioni complessive. Ma fino a quando non raggiungi quel punto, aggiungi più discussioni!

Tieni presente che ciò presuppone che i sistemi in questione siano dedicati alla tua app e che tu non debba giocare bene (evitare di morire di fame) altre app.


1
Puoi citare alcuni dei numeri che hai visto per il conteggio dei thread? Sarebbe utile solo averne un'idea. Grazie.
Kovac,

3

La risposta "big iron" è generalmente un thread per risorsa limitata - processore (CPU associato), arm (I / O associato), ecc. - ma funziona solo se è possibile instradare il lavoro al thread corretto per la risorsa a essere accessibile.

Laddove ciò non sia possibile, considera che hai risorse fungibili (CPU) e risorse non fungibili (armi). Per le CPU non è fondamentale assegnare ogni thread a una CPU specifica (anche se aiuta con la gestione della cache), ma per i bracci, se non è possibile assegnare un thread al braccio, si entra nella teoria dell'accodamento e qual è il numero ottimale per mantenere i bracci occupato. In genere sto pensando che se non puoi instradare le richieste in base al braccio utilizzato, allora avere 2-3 thread per braccio sarà corretto.

Una complicazione si presenta quando l'unità di lavoro passata al thread non esegue un'unità di lavoro ragionevolmente atomica. Ad esempio, potresti avere il thread ad un certo punto accedere al disco, ad un altro punto attendere su una rete. Ciò aumenta il numero di "crepe" in cui ulteriori thread possono entrare e svolgere un lavoro utile, ma aumenta anche l'opportunità per thread aggiuntivi di inquinare reciprocamente le cache, ecc. E bloccare il sistema.

Ovviamente, devi pesare tutto questo rispetto al "peso" di un filo. Sfortunatamente, la maggior parte dei sistemi ha thread molto pesanti (e quelli che chiamano "thread leggeri" spesso non sono affatto thread), quindi è meglio sbagliare sul lato basso.

Quello che ho visto in pratica è che differenze molto sottili possono fare un'enorme differenza nel numero di thread ottimali. In particolare, problemi di cache e conflitti di blocco possono limitare notevolmente la quantità di concorrenza pratica.


2

Una cosa da considerare è il numero di core presenti sulla macchina che eseguirà il codice. Ciò rappresenta un limite per il numero di thread che possono procedere in un determinato momento. Tuttavia, se, come nel tuo caso, si prevede che i thread attenderanno frequentemente l'esecuzione di una query da parte di un database, probabilmente vorrai ottimizzare i thread in base al numero di query simultanee che il database può elaborare.


2
no Il punto centrale dei thread era (prima che diventassero prevalenti i multiprocessore e più processori) è quello di essere in grado di simulare avere più processori su una macchina che ne ha solo uno. Ecco come ottenere interfacce utente reattive: un thread principale e thread ausiliari.
mmr

1
@mmr: Um no. L'idea dei thread è consentire il blocco dell'I / O e altre attività.
GEOCHET,

4
L'affermazione che ho fatto è stata che il numero di core su una macchina rappresenta un limite al numero di thread che possono funzionare in un dato momento, il che è un dato di fatto. Ovviamente altri thread possono attendere il completamento delle operazioni di I / O, e questa domanda è una considerazione importante.
newdayrising,

1
Comunque - hai GIL in Python, che rende i thread solo teoricamente paralleli. Non è possibile eseguire contemporaneamente più di 1 thread, quindi è importante solo la reattività e le operazioni di blocco.
Abgan,

2
+1 Per capire davvero come funzionano i computer. @mmr: devi capire che la differenza tra sembra avere più processori e ha più processori. @Rich B: un pool di thread è solo uno dei molti modi per gestire una raccolta di thread. È buono, ma certamente non l'unico.
addolorarsi il

2

Penso che questo sia un po 'una schivata alla tua domanda, ma perché non trasformarli in processi? La mia comprensione del networking (dai tempi nebulosi di un tempo, in realtà non codifico affatto le reti) era che ogni connessione in entrata può essere gestita come un processo separato, perché quindi se qualcuno fa qualcosa di brutto nel tuo processo, non lo fa nuke l'intero programma.


1
Per Python questo è particolarmente vero, poiché più processi possono essere eseguiti in parallelo, mentre più thread non lo fanno. Il costo è comunque piuttosto elevato. Devi avviare il nuovo interprete Python ogni volta e collegarti al DB con ogni processo (o utilizzare un reindirizzamento di pipe, ma ha anche un prezzo).
Abgan,

Il passaggio da un processo all'altro è, nella maggior parte dei casi, più costoso rispetto al passaggio da un thread all'altro (cambio di contesto intero anziché alcuni registri). Alla fine dipende molto dal tuo threading-lib. Poiché le domande ruotavano attorno al threading, presumo che i processi siano già fuori discussione.
Leonidas,

Giusto. Non sono sicuro del motivo per cui sto ottenendo un -2 al punteggio, tuttavia, a meno che la gente non voglia davvero vedere le risposte solo thread, piuttosto che includere altre risposte che funzionano.
mmr

@mmr: Considerando che la domanda era su / thread / pool, sì, penso che la gente dovrebbe aspettarsi una risposta sui thread.
GEOCHET,

La creazione del processo può essere eseguita una volta all'avvio (ovvero, un pool di processi anziché un pool di thread). Ammortizzato per la durata dell'applicazione, questo può essere piccolo. Non possono condividere facilmente le informazioni, ma ACQUISTA loro la possibilità di funzionare su più CPU, quindi questa risposta è utile. +1.
paxdiablo,

1

ryeguy, attualmente sto sviluppando un'applicazione simile e il mio numero di thread è impostato su 15. Sfortunatamente se lo aumento a 20, si blocca. Quindi, sì, penso che il modo migliore per gestirlo sia misurare se la tua configurazione attuale consente più o meno di un numero X di thread.


5
L'aggiunta al numero di thread non dovrebbe causare l'arresto anomalo casuale dell'app. C'è qualche motivo. Faresti bene a capire la causa perché potrebbe influire anche con meno thread in alcune circostanze, chi lo sa.
Matthew Lund,

-6

Nella maggior parte dei casi è necessario consentire al pool di thread di gestirlo. Se pubblichi un codice o fornisci ulteriori dettagli, potrebbe essere più facile vedere se c'è qualche motivo per cui il comportamento predefinito del pool di thread non sarebbe ottimale.

Puoi trovare maggiori informazioni su come dovrebbe funzionare qui: http://en.wikipedia.org/wiki/Thread_pool_pattern


1
@Pax: questa non sarebbe la prima volta che la maggior parte delle persone non vuole rispondere alla domanda (o capirla). Non sono preoccupato.
GEOCHET,

-10

Tanti thread quanti i core della CPU sono quelli che ho sentito molto spesso.


5
@Rich, almeno spiega perché :-). Questa regola empirica si applica solo quando tutti i thread sono associati alla CPU; ottengono una 'CPU' ciascuno. Quando molti dei thread sono associati all'I / O, di solito è meglio avere molti più thread di quelli della 'CPU (la CPU è quotata poiché si applica ai thread fisici di esecuzione, ad es. Core).
paxdiablo,

1
@Abgan, non ne ero sicuro, pensando che forse Python avrebbe creato thread del sistema operativo "reali" (eseguiti su più CPU). Se ciò che dici è vero (non ho motivo di dubitare), allora la quantità di CPU non ha cuscinetti - il threading è utile solo quando la maggior parte dei thread è in attesa di qualcosa (ad es. I / O DB).
paxdiablo,

1
@Rich: quando il threading (reale), conta il conteggio della CPU, poiché è possibile eseguire più thread non in attesa contemporaneamente. Con una CPU, solo una viene eseguita e il vantaggio deriva dall'avere molti altri thread in attesa di una risorsa non CPU.
paxdiablo

1
@Pax: non capisci il concetto di thread pool, quindi suppongo.
GEOCHET

1
@Rich, capisco bene i pool di thread; sembra che io (e altri qui) capisco anche l'hardware meglio di te. Con una CPU, può essere eseguito solo un thread di esecuzione, anche se ce ne sono altri in attesa di una CPU. Due CPU, due possono funzionare. Se tutti i thread sono in attesa di una CPU, il conteggio dei thread ideale è uguale a ...
paxdiablo,
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.