Quando avresti bisogno di "centinaia di migliaia" di thread?


31

Erlang, Go e Rust affermano tutti in un modo o nell'altro di supportare la programmazione concorrente con "thread" / coroutine economici. La FAQ di Go afferma:

È pratico creare centinaia di migliaia di goroutine nello stesso spazio degli indirizzi.

Il Tutorial Rust dice:

Poiché le attività sono significativamente più economiche da creare rispetto ai thread tradizionali, Rust può creare centinaia di migliaia di attività simultanee su un tipico sistema a 32 bit.

La documentazione di Erlang dice:

La dimensione heap iniziale predefinita di 233 parole è piuttosto conservativa al fine di supportare i sistemi Erlang con centinaia di migliaia o addirittura milioni di processi.

La mia domanda: che tipo di applicazione richiede così tanti thread di esecuzione simultanei? Solo i server Web più attivi ricevono anche migliaia di visitatori simultanei. Applicazioni di tipo capo-lavoratore / invio di lavoro che ho scritto restituiscono risultati decrescenti quando il numero di thread / processi è molto maggiore del numero di core fisici. Suppongo che potrebbe avere senso per le applicazioni numeriche, ma in realtà molte persone delegano il parallelismo a librerie di terzi scritte in Fortran / C / C ++, non in questi linguaggi di nuova generazione.


5
Penso che la fonte della tua confusione sia questa: questi microtread / compiti / ecc. Non sono principalmente intesi come sostituti dei thread / processi del SO di cui parli, né sono pensati per essere usati per dividere un grosso pezzo facilmente parallelizzabile di scricchiolii di numeri tra alcuni core (come hai osservato correttamente, non ha senso avere 100k thread su 4 core a tale scopo).
us2012,

1
Allora a cosa servono? Forse sono un ingenuo ma non ho mai incontrato una situazione in cui l'introduzione di coroutine / ecc. Avrebbe semplificato un programma a thread singolo di esecuzione. E sono stato in grado di raggiungere livelli "bassi" di concorrenza con i processi, che su Linux posso lanciare centinaia o migliaia senza sudare.
user39019,

Non avrebbe molto senso avere molti compiti effettivamente funzionanti. Ciò non significa che non potresti avere un gran numero di attività che per lo più erano semplicemente bloccate in attesa che accadesse qualcosa.
Loren Pechtel,

5
L'idea di asincronia basata su attività vs asincronia basata su thread è quella di dire che il codice utente dovrebbe concentrarsi sulle attività che devono accadere piuttosto che gestire i lavoratori che eseguono tali attività. Pensa a un thread come a un lavoratore che assumi; assumere un lavoratore è costoso e, se lo fai, vuoi che lavorino sodo su quante più attività possibili il 100% delle volte. Molti sistemi possono essere caratterizzati da centinaia o migliaia di attività in sospeso, ma non sono necessarie centinaia o migliaia di lavoratori.
Eric Lippert,

Continuando sul commento di @ EricLippert, ci sono diverse situazioni in cui esisterebbero centinaia di migliaia di attività. Esempio n. 1: la scomposizione di un'attività parallela ai dati, come l'elaborazione delle immagini. Esempio n. 2: un server che supporta centinaia di migliaia di client, ognuno dei quali potrebbe potenzialmente emettere un comando in qualsiasi momento. Ogni compito avrebbe richiesto il proprio "contesto di esecuzione leggero": la capacità di ricordare in che stato si trova (protocolli di comunicazione), il comando che sta attualmente eseguendo e poco altro. Leggero è possibile purché ciascuno abbia uno stack di chiamate superficiale.
rwong

Risposte:


19

un caso d'uso - websocket:
poiché i websocket sono di lunga durata rispetto alle semplici richieste, su un server occupato molti accumuli Web si accumulano nel tempo. microthreads ti dà una buona modellazione concettuale e anche un'implementazione relativamente semplice.

più in generale, i casi in cui numerose unità più o meno autonome attendono che si verifichino determinati eventi dovrebbero essere buoni casi d'uso.


15

Potrebbe essere utile pensare a ciò che Erlang è stato originariamente progettato per fare, ovvero gestire le telecomunicazioni. Attività come routing, commutazione, raccolta / aggregazione dei sensori, ecc.

Portalo nel mondo del web: considera un sistema come Twitter . Il sistema probabilmente non userebbe i microthread nella generazione di pagine web, ma potrebbe usarli nella sua raccolta / memorizzazione nella cache / distribuzione di tweet.

Questo articolo potrebbe essere di ulteriore aiuto.


11

In una lingua in cui non è consentito modificare le variabili, il semplice atto di mantenere lo stato richiede un contesto di esecuzione separato (che la maggior parte delle persone chiamerebbe un thread ed Erlang chiama un processo). Fondamentalmente, tutto è un lavoratore.

Considera questa funzione Erlang, che mantiene un contatore:

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

In un linguaggio OO convenzionale come C ++ o Java, lo faresti avendo una classe con un membro di classe privato, metodi pubblici per ottenere o modificare il suo stato e un oggetto istanziato per ogni contatore. Erlang sostituisce la nozione dell'oggetto istanziato con un processo, la nozione di metodi con messaggi e il mantenimento dello stato con le chiamate di coda che riavviano la funzione con qualunque valore costituisca il nuovo stato. Il vantaggio nascosto di questo modello - e la maggior parte della ragion d'essere di Erlang - è che il linguaggio serializza automaticamente l'accesso al valore del contatore attraverso l'uso di una coda di messaggi, rendendo il codice simultaneo molto facile da implementare con un alto grado di sicurezza .

Probabilmente sei abituato all'idea che i cambi di contesto siano costosi, il che è ancora vero dal punto di vista del sistema operativo host. Il runtime di Erlang è esso stesso un piccolo sistema operativo ottimizzato, quindi il passaggio tra i propri processi è rapido ed efficiente, il tutto mantenendo al minimo il numero di switch di contesto che il sistema operativo esegue. Per questo motivo, avere molte migliaia di processi non è un problema ed è incoraggiato.


1
La tua ultima applicazione di counter/1dovrebbe usare una c minuscola;) Ho provato a risolverlo, ma StackExchange non ama le modifiche a 1 carattere.
d11wtq

4

La mia domanda: che tipo di applicazione richiede così tanti thread di esecuzione simultanei?

1) Il fatto che una lingua "ridimensioni" significa che ci sono meno possibilità che tu debba abbandonare quella lingua quando le cose diventano più complesse lungo la strada. (Questo è chiamato il concetto di "Prodotto intero"). Molte persone stanno abbandonando Apache per Nginx proprio per questo motivo. Se sei vicino al "limite rigido" imposto dal sovraccarico del thread, avrai paura e inizierai a pensare a come superarlo. I siti Web non possono mai prevedere quanto traffico otterranno, quindi è ragionevole dedicare un po 'di tempo a rendere le cose scalabili.

2) Una goroutine per richiesta è solo l'inizio. Ci sono molte ragioni per usare le goroutine internamente.

  • Prendi in considerazione un'app Web con 100 richieste simultanee, ma ogni richiesta genera 100 richieste back-end. L'esempio ovvio è un aggregatore di motori di ricerca. Ma praticamente qualsiasi app potrebbe creare goroutine per ogni "area" sullo schermo, quindi generarle indipendentemente anziché in sequenza. Ad esempio, ogni pagina su Amazon.com è composta da oltre 150 richieste di back-end, assemblate apposta per te. Non si nota perché sono in parallelo, non in sequenza, e ogni "area" è il proprio servizio web.
  • Prendi in considerazione qualsiasi app in cui affidabilità e latenza sono fondamentali. Probabilmente si desidera che ogni richiesta in arrivo emetta alcune richieste di back-end e si restituisca qualsiasi dato ritorni per primo .
  • Prendi in considerazione qualsiasi "iscrizione client" eseguita nella tua app. Invece di dire "per ogni elemento, ottenere i dati", è possibile estrarre un gruppo di goroutine. Se hai un sacco di DB slave da interrogare, andrai magicamente N più velocemente. In caso contrario, non sarà più lento.

i rendimenti decrescenti colpiti quando il numero di thread / processi è molto maggiore del numero di core fisici

Le prestazioni non sono l'unico motivo per suddividere un programma in CSP . Può effettivamente rendere il programma più facile da capire e alcuni problemi possono essere risolti con molto meno codice.

Come nelle diapositive collegate sopra, avere la concorrenza nel codice è un modo per organizzare il problema. Non avere goroutine è come non avere una struttura di dati Map / Dictonary / Hash nella tua lingua. Puoi cavartela senza di essa. Ma una volta che lo hai, inizi a usarlo ovunque e semplifica davvero il tuo programma.

In passato, ciò significava "eseguire il rollup della propria" programmazione multithread. Ma questo era complesso e pericoloso - non ci sono ancora molti strumenti per assicurarsi che non stai creando razze. E come evitare che un futuro manutentore commetta un errore? Se guardi programmi grandi / complessi, vedrai che spendono MOLTE risorse in quella direzione.

Poiché la concorrenza non è una parte di prima classe della maggior parte delle lingue, i programmatori di oggi hanno un punto cieco sul perché sarebbe utile per loro. Ciò diventerà più evidente solo quando ogni telefono e orologio da polso si dirige verso 1000 core. Vai navi con uno strumento di rilevamento gara integrato.


2

Per Erlang è comune avere un processo per connessione o altra attività. Ad esempio, un server audio in streaming potrebbe avere 1 processo per utente connesso.

La VM Erlang è ottimizzata per gestire migliaia o addirittura centinaia di migliaia di processi rendendo gli switch di contesto molto economici.


1

Convenienza. Quando ho iniziato a fare la programmazione multi-thread, stavo facendo un sacco di simulazione e sviluppo di giochi per divertimento. Ho trovato molto utile semplicemente girare un thread per ogni singolo oggetto e lasciarlo fare le sue cose piuttosto che elaborare ciascuno attraverso un ciclo. Se il tuo codice non è disturbato da comportamenti non deterministici e non hai collisioni, può semplificare la codifica. Con la potenza a nostra disposizione ora, se dovessi tornare a quello, posso facilmente immaginare di girare un paio di migliaia di thread a causa della potenza di elaborazione e della memoria sufficienti per gestire tanti oggetti discreti!


1

Un semplice esempio per Erlang, progettato per la comunicazione: trasferimento di pacchetti di rete. Quando si esegue una richiesta http, è possibile che si disponga di migliaia di pacchetti TCP / IP. Aggiungi a questo che tutti si collegano contemporaneamente e hai il tuo caso d'uso.

Prendi in considerazione molte applicazioni utilizzate internamente da qualsiasi grande azienda per gestire i propri ordini o qualsiasi cosa di cui possano aver bisogno. I server Web non sono l'unica cosa che necessita di thread.


-2

Qui vengono in mente alcune attività di rendering. Se stai eseguendo una lunga catena di operazioni su ogni pixel di un'immagine e se tali operazioni sono parallelizzabili, anche un'immagine 1024x768 relativamente piccola si trova proprio nella parentesi "centinaia di migliaia".


2
Alcuni anni fa, ho trascorso alcuni anni a elaborare immagini FLIR in tempo reale, a sgranocchiare immagini 256x256 a 30 fotogrammi al secondo. A meno che tu non abbia MOLTI processori HARDWARE e un modo SEAMLESS di partizionare i tuoi dati tra loro, l'ultima cosa che vuoi fare è aggiungere il cambio di contesto, la contesa di memoria e il thrashing della cache ai costi di calcolo effettivi.
John R. Strohm,

Dipende dal lavoro svolto. Se tutto ciò che stai facendo è consegnare un lavoro a un nucleo hardware / unità di esecuzione, dopo di che puoi dimenticartene efficacemente (e notare che questo è il modo in cui funzionano le GPU, quindi questo non è uno scenario ipotetico) quindi l'approccio è valido.
Maximus Minimus,
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.