Cosa possono fare più thread che un singolo thread non può? [chiuso]

98

Mentre i thread possono velocizzare l'esecuzione del codice, sono effettivamente necessari? È possibile eseguire ogni pezzo di codice utilizzando un singolo thread o esiste qualcosa che può essere realizzato utilizzando più thread?

    
posta AngryBird 01.08.2011 - 17:29
fonte

13 risposte

111

Prima di tutto, i thread non possono accelerare l'esecuzione del codice. Non fanno funzionare il computer più velocemente. Tutto quello che possono fare è aumentare l'efficienza del computer utilizzando il tempo che altrimenti andrebbe sprecato. In alcuni tipi di elaborazione questa ottimizzazione può aumentare l'efficienza e ridurre i tempi di esecuzione.

La semplice risposta è sì. È possibile scrivere qualsiasi codice da eseguire su un singolo thread. Dimostrazione: un sistema a processore singolo può eseguire solo le istruzioni in modo lineare. Avere più linee di esecuzione viene eseguito dagli interrupt di elaborazione del sistema operativo, salvando lo stato del thread corrente e iniziando un altro.

La risposta complessa è ... più complessa! La ragione per cui i programmi multithread spesso possono essere più efficienti di quelli lineari è a causa di un "problema" hardware. La CPU può eseguire calcoli più rapidamente rispetto alla memoria e all'IO di archiviazione rigida. Quindi, un'istruzione "add", ad esempio, viene eseguita molto più rapidamente di un "fetch". Le cache e il recupero delle istruzioni del programma dedicato (non sono sicuro del termine esatto qui) possono in qualche misura combatterlo, ma il problema di velocità rimane.

Il threading è un modo per combattere questa discrepanza usando la CPU per le istruzioni legate alla CPU mentre le istruzioni IO stanno completando. Un tipico piano di esecuzione del thread probabilmente sarebbe: recupera dati, elabora dati, scrive dati. Supponiamo che il recupero e la scrittura richiedano 3 cicli e che l'elaborazione ne faccia uno, a scopo illustrativo. Puoi vedere che mentre il computer sta leggendo o scrivendo, sta facendo niente per 2 cicli ciascuno? Chiaramente è pigro e dobbiamo rompere la nostra frusta per l'ottimizzazione!

Possiamo riscrivere il processo utilizzando il threading per utilizzare questo tempo sprecato:

  1. # 1 recupero
  2. nessuna operazione
  3. # 2 recupero
  4. n. 1, elaboralo
  5. scrivi # 1
  6. # 1 recupero
  7. # 2 fatto, lo elabora
  8. scrivi # 2
  9. recupero # 2

E così via. Ovviamente questo è un esempio un po 'forzato, ma puoi vedere come questa tecnica può utilizzare il tempo che altrimenti sarebbe trascorso in attesa di IO.

Si noti che il threading come mostrato sopra può solo aumentare l'efficienza su processi molto legati all'IO. Se un programma si basa principalmente sul calcolo delle cose, non ci saranno molti "buchi" in cui potremmo fare più lavoro. Inoltre, c'è un sovraccarico di diverse istruzioni quando si passa da un thread all'altro. Se si eseguono troppi thread, la CPU impiegherà la maggior parte del tempo necessario per il passaggio e non si lavorerà molto sul problema. Questo è chiamato thrashing .

Tutto va bene per un processore single core, ma la maggior parte dei processori moderni ha due o più core. I thread hanno ancora lo stesso scopo: massimizzare l'utilizzo della CPU, ma questa volta abbiamo la possibilità di eseguire due istruzioni separate allo stesso tempo. può diminuire il tempo di esecuzione di un fattore di molti core disponibili, perché il computer è in realtà multitasking, non il cambio di contesto.

Con più core, i thread forniscono un metodo per suddividere il lavoro tra i due core. Quanto sopra vale comunque per ogni singolo core; Molto probabilmente un programma che esegue un'efficienza massima con due thread su un core funzionerà al massimo dell'efficienza con circa quattro thread su due core. (Qui l'efficienza viene misurata con le esecuzioni delle istruzioni NOP minime).

I problemi con l'esecuzione di thread su più core (al contrario di un singolo core) sono generalmente risolti dall'hardware. La CPU sarà sicura di bloccare le posizioni di memoria appropriate prima di leggere / scrivere su di essa. (Ho letto che utilizza un bit di flag speciale in memoria per questo, ma questo potrebbe essere ottenuto in diversi modi.) Come programmatore con linguaggi di livello superiore, non devi preoccuparti di nulla di più su due core come te dovrebbe con uno.

TL; DR: I thread possono suddividere il lavoro in modo da consentire al computer di elaborare diverse attività in modo asincrono. Ciò consente al computer di funzionare alla massima efficienza utilizzando tutto il tempo di elaborazione disponibile, anziché bloccarlo quando un processo è in attesa di una risorsa.

    
risposta data 12.09.2013 - 14:57
fonte
37

What can multiple threads do that a single thread cannot?

Niente.

Schermata di prova semplice:

  • [congettura di Church-Turing] ⇒ Tutto ciò che può essere calcolato può essere calcolato da una Universal Turing Machine.
  • Una Universal Turing Machine è a thread singolo.
  • Ergo, tutto ciò che può essere calcolato può essere calcolato da un singolo thread.

Si noti, tuttavia, che vi è una grande supposizione nascosta: vale a dire che la lingua utilizzata all'interno del thread singolo è completata da Turing.

Quindi, la domanda più interessante sarebbe: "È possibile aggiungere solo multi-threading a un linguaggio non completo di Turing per renderlo completo?" E credo che la risposta sia "Sì".

Prendiamo le lingue funzionali totali. [Per chi non ha familiarità: proprio come la programmazione funzionale è la programmazione con le funzioni, la programmazione funzionale totale è la programmazione con le funzioni totali.]

Le lingue funzionali totali non sono ovviamente complete di Turing: non puoi scrivere un ciclo infinito in un TFPL (infatti, è praticamente la definizione di "totale"), ma tu puoi in una macchina di Turing, ergo esiste almeno un programma che non può essere scritto in un TFPL ma può essere in un UTM, quindi i TFPL sono meno potenti dal punto di vista computazionale rispetto alle UTM.

Tuttavia, non appena si aggiunge il thread a un TFPL, si ottengono loop infiniti: basta fare ogni iterazione del ciclo in un nuovo thread. Ogni singolo thread restituisce sempre un risultato, quindi è Total, ma ogni thread genera anche un nuovo thread che esegue l'iterazione next , ad infinitum.

I penso che questa lingua sia completa da Turing.

Per lo meno, risponde alla domanda originale:

What can multiple threads do that a single thread cannot?

Se hai una lingua che non può fare cicli infiniti, allora multi-threading ti permette di fare loop infiniti.

Nota, ovviamente, che generare un thread è un effetto collaterale e quindi la nostra lingua estesa non solo non è più Totale, non è nemmeno più funzionale.

    
risposta data 01.08.2011 - 19:33
fonte
22

In teoria, tutto ciò che fa un programma multithreading può essere fatto anche con un programma a thread singolo, solo più lento.

In pratica, la differenza di velocità può essere così grande che non è possibile utilizzare un programma a thread singolo per l'attività. Per esempio. se si ha un processo di elaborazione dati batch in esecuzione ogni notte e sono necessarie più di 24 ore per terminare su un singolo thread, non è possibile fare altro che renderlo multithread. (In pratica, la soglia è probabilmente ancora meno: spesso tali attività di aggiornamento devono terminare entro la mattina presto, prima che gli utenti inizino a utilizzare nuovamente il sistema. Inoltre, altri compiti possono dipendere da loro, che devono finire anche durante la stessa notte. il runtime disponibile può essere di poche ore / minuti.)

Il lavoro di elaborazione su più thread è una forma di elaborazione distribuita; stai distribuendo il lavoro su più thread. Un altro esempio di elaborazione distribuita (che utilizza più computer invece di più thread) è lo screensaver SETI: scricchiolare molti dati di misura su un singolo processore richiederebbe molto tempo e i ricercatori preferirebbero vedere i risultati prima del pensionamento ;-) Tuttavia, essi non hanno il budget per affittare un supercomputer per così tanto tempo, quindi distribuiscono il lavoro su milioni di PC domestici, per renderlo economico.

    
risposta data 01.08.2011 - 17:51
fonte
11

Although threads seem to be a small step from sequential computation, in fact, they represent a huge step. They discard the most essential and appealing properties of sequential computation: understandability, predictability, and determinism. Threads, as a model of computation, are wildly nondeterministic, and the job of the programmer becomes one of pruning that nondeterminism.

-- The Problem with Threads (www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf).

Mentre ci sono alcuni vantaggi in termini di prestazioni che si possono avere usando thread in cui è possibile distribuire il lavoro su più core, spesso si ottengono un ottimo prezzo.

Uno degli svantaggi dell'utilizzo di thread non menzionati ancora qui è la perdita di compartimentazione delle risorse che si ottiene con spazi di processo a thread singolo. Ad esempio, supponiamo di incontrare il caso di un segfault. In alcuni casi è possibile recuperare da questo in un'applicazione multi-processo, in quanto si lascia semplicemente morire il bambino che genera l'errore e ne viene rigenerato uno nuovo. Questo è il caso nel backend del prefork di Apache. Quando una istanza di httpd diventa obsoleta, il caso peggiore è che la richiesta HTTP particolare può essere abbandonata per quel processo, ma Apache genera un nuovo figlio e spesso la richiesta se appena inviato e servito. Il risultato finale è che Apache nel suo complesso non viene rimosso con il thread difettoso.

Un'altra considerazione in questo scenario è la perdita di memoria. Ci sono alcuni casi in cui puoi gestire con garbo un thread che si blocca (su UNIX, il ripristino da alcuni segnali specifici - anche segfault / fpviolation - è possibile), ma anche in quel caso, potresti aver perso tutta la memoria allocata da quel thread (malloc, nuovo, ecc.). Quindi, mentre il processo può continuare a vivere, perde sempre più memoria nel tempo con ogni errore / ripristino. Ancora una volta, ci sono alcuni modi per minimizzare questo tipo di utilizzo dei pool di memoria di Apache. Ma questo non impedisce la memoria che potrebbe essere stata allocata da librerie di terze parti che il thread potrebbe aver utilizzato.

E, come alcune persone hanno sottolineato, comprendere le primitive di sincronizzazione è forse la cosa più difficile da ottenere davvero. Questo problema di per sé - solo ottenere la logica generale giusta per tutto il codice - può essere un enorme problema. Misteriosi deadlock sono inclini ad accadere nei momenti più strani, ea volte nemmeno fino a quando il tuo programma non è in esecuzione in produzione, il che rende il debug ancora più difficile. A questo si aggiunge il fatto che le primitive di sincronizzazione spesso variano ampiamente con la piattaforma (Windows rispetto a POSIX), e il debugging può essere spesso più difficile, così come la possibilità per le condizioni di gara in qualsiasi momento (avvio / inizializzazione, runtime e spegnimento), programmare con i thread ha davvero poca pietà per i principianti. E anche per gli esperti, c'è ancora poca misericordia solo perché la conoscenza del threading stesso non minimizza la complessità in generale. Ogni linea di codice filettato a volte sembra aumentare in modo esponenziale la complessità complessiva del programma e aumentare la probabilità che una situazione di stallo nascosta o di una strana condizione di razza possa affiorare in qualsiasi momento. Può anche essere molto difficile scrivere casi di test per scoprire queste cose.

Questo è il motivo per cui alcuni progetti come Apache e PostgreSQL sono per la maggior parte basati sui processi. PostgreSQL esegue ogni thread di backend in un processo separato. Naturalmente questo non allevia il problema della sincronizzazione e delle condizioni di gara, ma aggiunge un po 'di protezione e in qualche modo semplifica le cose.

Più processi, ognuno dei quali esegue un singolo thread di esecuzione, può essere molto meglio di più thread in esecuzione in un singolo processo. E con l'avvento di gran parte del nuovo codice peer-to-peer come AMQP (RabbitMQ, Qpid, ecc.) E ZeroMQ, è molto più semplice suddividere i thread tra diversi spazi di processo e persino macchine e reti, semplificando notevolmente le cose. Ma ancora, non è un proiettile d'argento. C'è ancora complessità da affrontare. Basta spostare alcune delle variabili dallo spazio del processo alla rete.

La linea di fondo è che la decisione di entrare nel dominio dei thread non è una decisione chiara. Una volta che entri in quel territorio, quasi istantaneamente tutto diventa più complesso e intere nuove problematiche entrano nella tua vita. Può essere divertente e fico, ma è come l'energia nucleare - quando le cose vanno male, possono andare male e velocemente. Ricordo di aver frequentato un corso di critica durante l'allenamento per la critica molti anni fa e hanno mostrato le foto di alcuni scienziati di Los Alamos che hanno suonato con il plutonio nei laboratori della seconda guerra mondiale. Molti hanno preso poca o nessuna precauzione contro l'evento di un'esposizione, e in un batter d'occhio - in un solo flash luminoso e indolore, tutto sarebbe finito per loro. Alcuni giorni dopo erano morti. Richard Feynman in seguito si riferì a questo come " solleticando la coda del drago ." Questo è come giocare con i thread può essere come (almeno per me comunque). All'inizio sembra piuttosto innocuo, e quando sei morso, ti graffi la testa per quanto velocemente le cose sono diventate acide. Ma almeno i thread non ti uccideranno.

    
risposta data 01.08.2011 - 22:24
fonte
10

Prima di tutto, un'applicazione a thread singolo non trarrà mai vantaggio da una CPU multi-core o hyper-threading. Ma anche su un singolo core, la CPU a thread singolo con multi-threading ha dei vantaggi.

Considera l'alternativa e se questo ti rende felice. Supponiamo di avere più attività che devono essere eseguite contemporaneamente. Ad esempio, devi continuare a comunicare con due sistemi diversi. Come si fa a fare questo senza multi-threading? Probabilmente creerai il tuo programma di pianificazione e lascerai chiamare le diverse attività che devono essere eseguite. Ciò significa che è necessario suddividere le attività in parti. Probabilmente hai bisogno di rispettare alcuni vincoli in tempo reale per assicurarti che le tue parti non occupino troppo tempo. Altrimenti il timer scadrà in altre attività. Questo rende più difficile la suddivisione di un compito. Più attività hai bisogno di gestire da te, più dividi devi fare e più complesso sarà lo scheduler per soddisfare tutti i vincoli.

Quando hai più thread la vita può diventare più facile. Un programma di pianificazione preventiva può interrompere un thread in qualsiasi momento, mantenere il suo stato e ri (avviare) un altro. Si riavvierà quando il tuo thread avrà il suo turno. Vantaggi: la complessità della scrittura di uno schedulatore è già stata fatta per te e non devi dividere le tue attività. Inoltre, lo scheduler è in grado di gestire processi / thread di cui tu stesso non sei nemmeno a conoscenza. Inoltre, quando un thread non ha bisogno di fare nulla (è in attesa di qualche evento), non richiederà alcun ciclo della CPU. Questo non è così facile da realizzare quando si crea il proprio scheduler down-thread singolo. (mettere qualcosa per dormire non è così difficile, ma come si sveglia?)

Lo svantaggio dello sviluppo multi-thread è che è necessario comprendere i problemi di concorrenza, le strategie di blocco e così via. Lo sviluppo di codice multi-threaded senza errori può essere piuttosto difficile. E il debugging può essere ancora più difficile.

    
risposta data 01.08.2011 - 17:20
fonte
9

is there something that exists that can only be accomplished by using multiple threads?

Sì. Non è possibile eseguire codice su più CPU o core CPU con un singolo thread.

Senza più CPU / core, i thread possono ancora semplificare il codice che concettualmente funziona in parallelo, come la gestione client su un server - ma si potrebbe fare la stessa cosa senza fili.

    
risposta data 01.08.2011 - 22:46
fonte
6

I thread non riguardano solo la velocità ma la concorrenza.

Se non hai un'applicazione batch come suggerito da @Peter ma invece un toolkit GUI come WPF, come puoi interagire con gli utenti e la logica di business con un solo thread?

Supponiamo inoltre che tu stia creando un server Web. Come potresti servire più di un utente contemporaneamente con un solo thread (supponendo che non ci siano altri processi)?

Ci sono molti scenari in cui solo un thread semplice non è sufficiente. Ecco perché i recenti progressi come il processore Intel MIC con oltre 50 core e centinaia di thread sono in corso.

Sì, la programmazione parallela e simultanea è difficile. Ma necessario.

    
risposta data 01.08.2011 - 17:07
fonte
6

Il multi-threading consente all'interfaccia GUI di essere reattiva durante le lunghe operazioni di elaborazione. Senza multi-threading, l'utente sarebbe bloccato a guardare un modulo bloccato mentre è in esecuzione un processo lungo.

    
risposta data 01.08.2011 - 20:05
fonte
5

Il codice multi-threaded può bloccare la logica del programma e accedere a dati non aggiornati in modi che i singoli thread non possono.

I thread possono prendere un bug oscuro da qualcosa che ci si può aspettare che un programmatore medio esegua il debug e lo trasferisca nel reame in cui vengono raccontate le storie della fortuna necessaria per catturare lo stesso bug con i pantaloni abbassati quando un programmatore di allerta stava cercando al momento giusto.

    
risposta data 01.08.2011 - 21:58
fonte
4

Le app che si occupano di bloccare l'IO che devono anche rimanere reattivi ad altri input (la GUI o altre connessioni) non possono essere rese singlethreaded

l'aggiunta di metodi di controllo nella lib di IO per vedere quanto può essere letto senza bloccare può aiutare questo ma non molte librerie fanno tutte le garanzie su questo

    
risposta data 01.08.2011 - 17:09
fonte
4

Un sacco di buone risposte, ma non sono sicuro che qualsiasi frase sia così come vorrei - Forse questo offre un modo diverso di guardarlo:

I thread sono solo una semplificazione della programmazione come oggetti o attori o loop (Sì, qualsiasi cosa tu implementi con i loop che puoi implementare con if / goto).

Senza discussioni si implementa semplicemente un motore di stato. Ho dovuto farlo molte volte (la prima volta che l'ho fatto non ne avevo mai sentito parlare - ho appena fatto una grande dichiarazione di switch controllata da una variabile "State"). Le macchine di stato sono ancora abbastanza comuni ma possono essere fastidiose. Con i fili un grosso pezzo della piastra si spegne.

Inoltre, rendono più facile per una lingua interrompere la sua esecuzione in runtime in blocchi compatibili con più CPU (così fanno gli attori, credo).

Java fornisce thread "verdi" su sistemi in cui il sistema operativo non fornisce alcun supporto per il threading. In questo caso è più facile vedere che non sono nient'altro che un'astrazione di programmazione.

    
risposta data 02.08.2011 - 05:38
fonte
0

I sistemi operativi utilizzano il concetto di time slicing in cui ogni thread ottiene il tempo di essere eseguito e quindi viene anticipato. Approccio del genere può sostituire il threading così com'è ora, ma scrivere i propri scheduler in ogni applicazione sarebbe eccessivo. Inoltre, dovresti lavorare con dispositivi I / O e così via. E richiederebbe un po 'di supporto dal lato hardware, in modo da poter attivare gli interrupt per far funzionare il programma di pianificazione. Fondamentalmente staresti scrivendo un nuovo sistema operativo ogni volta.

In generale il threading può migliorare le prestazioni nei casi in cui i thread attendono l'I / O o stanno dormendo. Consente inoltre di rendere le interfacce reattive e di consentire l'interruzione dei processi mentre si eseguono attività lunghe. Inoltre, il threading migliora le cose sulle CPU multicore reali.

    
risposta data 01.08.2011 - 17:20
fonte
0

In primo luogo, i thread possono fare due o più cose contemporaneamente (se hai più di un core). Anche se puoi farlo anche con più processi, alcune attività semplicemente non si distribuiscono su più processi molto bene.

Inoltre, alcune attività contengono degli spazi che non puoi facilmente evitare. Ad esempio, è difficile leggere i dati da un file su disco e fare in modo che il tuo processo faccia qualcos'altro allo stesso tempo. Se il tuo compito richiede necessariamente molti dati di lettura dal disco, il tuo processo impiegherà molto tempo ad attendere il disco indipendentemente da ciò che fai.

In secondo luogo, i thread possono consentire di evitare di dover ottimizzare grandi quantità del codice che non è critico dal punto di vista delle prestazioni. Se hai un solo thread, ogni pezzo di codice è critico per le prestazioni. Se si blocca, si è affondati - nessun compito che potrebbe essere svolto da quel processo può far progredire l'avanzamento. Con i thread, un blocco interesserà solo quel thread e altri thread possono venire avanti e lavorare sulle attività che devono essere eseguite da quel processo.

Un buon esempio è il codice di gestione degli errori eseguito raramente. Supponiamo che un'attività riscontri un errore molto raro e che il codice per gestire l'errore debba essere inserito nella memoria. Se il disco è occupato e il processo ha solo un singolo thread, non è possibile avanzare in avanti finché il codice per gestire quell'errore non può essere caricato in memoria. Ciò può causare una risposta bursty.

Un altro esempio è se si potrebbe raramente fare una ricerca nel database. Se aspetti che il database risponda, il tuo codice arriverà a un enorme ritardo. Ma non vuoi prendere la briga di rendere tutto questo codice asincrono perché è così raro che devi fare queste ricerche. Con una discussione per fare questo lavoro, ottieni il meglio da entrambi i mondi. Un thread per fare questo lavoro lo rende non critico come dovrebbe essere.

    
risposta data 13.08.2011 - 16:16
fonte

Leggi altre domande sui tag