Differenza tra un "coroutine" e un "thread"?


Risposte:


122

Le coroutine sono una forma di elaborazione sequenziale: solo una è in esecuzione in un dato momento (proprio come le procedure AKA delle subroutine - Le funzioni AKA - si passano il testimone tra loro in modo più fluido).

I thread sono (almeno concettualmente) una forma di elaborazione simultanea: più thread possono essere eseguiti in qualsiasi momento. (Tradizionalmente, su macchine single-CPU, single-core, la concorrenza è stata simulata con un po 'di aiuto dal sistema operativo - al giorno d'oggi, poiché così tante macchine sono multi-CPU e / o multi-core, i thread verranno di fatto eseguiti simultaneamente, non solo "concettualmente").


188

Prima lettura: concorrenza vs parallelismo - Qual è la differenza?

La concorrenza è la separazione delle attività per fornire l'esecuzione interfogliata. Il parallelismo è l'esecuzione simultanea di più pezzi di lavoro al fine di aumentare la velocità. - https://github.com/servo/servo/wiki/Design

Risposta breve: con i thread, il sistema operativo commuta i thread in esecuzione preventivamente in base al suo scheduler, che è un algoritmo nel kernel del sistema operativo. Con le coroutine, il programmatore e il linguaggio di programmazione determinano quando cambiare le coroutine; in altre parole, le attività sono multitasking in modo cooperativo mettendo in pausa e riprendendo le funzioni ai set point, tipicamente (ma non necessariamente) all'interno di un singolo thread.

Risposta lunga: Contrariamente ai thread, che sono preventivamente programmati dal sistema operativo, gli switch coroutine sono cooperativi, il che significa che il programmatore (e forse il linguaggio di programmazione e il suo runtime) controlla quando si verifica uno switch.

Contrariamente ai thread, che sono preventivi, gli switch coroutine sono cooperativi (il programmatore controlla quando si verifica un interruttore). Il kernel non è coinvolto negli switch coroutine. - http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/overview.html

Un linguaggio che supporta i thread nativi può eseguire i suoi thread (thread utente) sui thread del sistema operativo ( thread del kernel ). Ogni processo ha almeno un thread del kernel. I thread del kernel sono come processi, tranne per il fatto che condividono lo spazio di memoria nel proprio processo con tutti gli altri thread in quel processo. Un processo "possiede" tutte le risorse assegnate, come memoria, handle di file, socket, handle di dispositivo, ecc. E queste risorse sono tutte condivise tra i thread del kernel.

Lo scheduler del sistema operativo fa parte del kernel che esegue ogni thread per un certo periodo di tempo (su un singolo computer processore). Lo scheduler alloca il tempo (timeslicing) a ciascun thread, e se il thread non è finito entro quel tempo, lo scheduler lo impedisce (lo interrompe e passa a un altro thread). Più thread possono essere eseguiti in parallelo su una macchina a più processori, poiché ogni thread può essere (ma non deve necessariamente essere) programmato su un processore separato.

Su una singola macchina con processore, i thread sono suddivisi in tempi e preemptati (scambiati) rapidamente (su Linux il timeslice predefinito è 100ms) che li rende simultanei. Tuttavia, non possono essere eseguiti in parallelo (contemporaneamente), poiché un processore single-core può eseguire solo una cosa alla volta.

Le coroutine e / o i generatori possono essere utilizzati per implementare funzioni di cooperazione. Invece di essere eseguiti sui thread del kernel e pianificati dal sistema operativo, vengono eseguiti in un singolo thread fino a quando non si arrendono o finiscono, cedendo ad altre funzioni come determinato dal programmatore. I linguaggi con generatori , come Python e ECMAScript 6, possono essere usati per costruire coroutine. Async / await (visto in C #, Python, ECMAscript 7, Rust) è un'astrazione costruita sulla base delle funzioni del generatore che producono futures / promesse.

In alcuni contesti, le coroutine possono fare riferimento a funzioni impilate mentre i generatori possono fare riferimento a funzioni impilate.

Fibre , fili leggeri e fili verdi sono altri nomi per coroutine o cose simili a coroutine. A volte possono sembrare (tipicamente di proposito) più simili ai thread del sistema operativo nel linguaggio di programmazione, ma non funzionano in parallelo come thread reali e funzionano invece come coroutine. (Potrebbero esserci particolarità tecniche più specifiche o differenze tra questi concetti a seconda della lingua o dell'implementazione.)

Ad esempio, Java aveva " thread verdi "; questi erano thread pianificati dalla Java virtual machine (JVM) invece che nativamente sui thread del kernel del sistema operativo sottostante. Questi non funzionavano in parallelo o sfruttavano più processori / core, poiché ciò richiederebbe un thread nativo! Dato che non erano programmati dal sistema operativo, erano più simili alle coroutine che ai thread del kernel. I thread verdi sono quelli utilizzati da Java fino all'introduzione dei thread nativi in ​​Java 1.2.

Le discussioni consumano risorse. Nella JVM, ogni thread ha il proprio stack, in genere 1 MB di dimensione. 64k è la quantità minima di spazio di stack consentita per thread nella JVM. Le dimensioni dello stack di thread possono essere configurate sulla riga di comando per JVM. Nonostante il nome, i thread non sono gratuiti, a causa del loro uso di risorse come ogni thread che necessita del proprio stack, dell'archiviazione locale del thread (se presente) e del costo della pianificazione del thread / cambio di contesto / invalidazione della cache della CPU. Questo è uno dei motivi per cui le coroutine sono diventate popolari per applicazioni con prestazioni critiche e concomitanti.

Mac OS consentirà a un processo di allocare circa 2000 thread e Linux alloca stack da 8 MB per thread e consentirà solo il numero di thread che si adatta alla RAM fisica.

Quindi, i thread sono il peso più pesante (in termini di utilizzo della memoria e tempo di cambio di contesto), quindi le coroutine e infine i generatori sono il peso più leggero.


2
+1, ma questa risposta potrebbe beneficiare di alcuni riferimenti.
Kojiro,

1
I fili verdi sono qualcosa di diverso rispetto alle coroutine. non lo sono? Anche le fibre hanno alcune differenze. vedi programmers.stackexchange.com/questions/254140/…

113

Circa 7 anni di ritardo, ma alle risposte mancano alcuni contesti su co-routine vs discussioni. Perché le coroutine stanno ricevendo così tanta attenzione ultimamente e quando dovrei usarle rispetto ai thread ?

Prima di tutto se le coroutine corrono contemporaneamente (mai in parallelo ), perché qualcuno le preferirebbe ai thread?

La risposta è che le coroutine possono fornire un livello molto elevato di concorrenza con un sovraccarico minimo . Generalmente in un ambiente con thread hai al massimo 30-50 thread prima che la quantità di overhead sprecata che pianifichi effettivamente questi thread (dallo scheduler di sistema) riduca significativamente la quantità di tempo in cui i thread svolgono effettivamente un lavoro utile.

Ok, quindi con i thread puoi avere parallelismo, ma non troppo parallelismo, non è ancora meglio di una co-routine in esecuzione in un singolo thread? Beh, non necessariamente. Ricorda che una co-routine può ancora fare concorrenza senza sovraccarico dello scheduler - gestisce semplicemente il cambio di contesto stesso.

Ad esempio, se hai una routine che fa un po 'di lavoro ed esegue un'operazione che sai bloccherà per un po' di tempo (ad esempio una richiesta di rete), con una co-routine puoi passare immediatamente a un'altra routine senza il sovraccarico di includere lo scheduler di sistema in questa decisione: sì, è necessario che il programmatore specifichi quando è possibile passare da una routine all'altra.

Con un sacco di routine che svolgono pochissime parti di lavoro e si alternano volontariamente tra loro, hai raggiunto un livello di efficienza che nessun programmatore avrebbe mai potuto sperare di raggiungere. Ora puoi avere migliaia di coroutine che lavorano insieme anziché decine di fili.

Poiché ora le routine passano tra di loro a punti predeterminati, è ora possibile evitare di bloccare strutture di dati condivise (poiché non si direbbe mai al codice di passare a un'altra procedura centrale nel mezzo di una sezione critica)

Un altro vantaggio è l'utilizzo della memoria molto più basso. Con il modello threaded, ogni thread deve allocare il proprio stack e pertanto l'utilizzo della memoria aumenta in modo lineare con il numero di thread disponibili. Con le co-routine, il numero di routine che hai non ha una relazione diretta con l'utilizzo della memoria.

E infine, le co-routine stanno ricevendo molta attenzione perché in alcuni linguaggi di programmazione (come Python) i tuoi thread non possono comunque funzionare in parallelo - funzionano simultaneamente proprio come le coroutine, ma senza memoria insufficiente e spese generali di programmazione gratuite.


2
Come fare un passaggio a un'altra attività nelle coroutine quando incontriamo un'operazione di blocco?
Narcisse Doudieu Siewe,

Il modo in cui passi a un'altra attività consiste nel fare in modo che qualsiasi operazione di blocco venga effettivamente eseguita in modo asincrono. Ciò significa che devi evitare di utilizzare qualsiasi operazione che potrebbe effettivamente bloccare e utilizzare solo operazioni che supportano il non blocco quando utilizzato nel tuo sistema coroutine. L'unico modo per aggirare questo problema è avere coroutine supportate dal kernel, come ad esempio UMS su Windows, dove salta nel tuo scheduler ogni volta che il tuo "thread" UMS si blocca su un syscall.
retep998

@MartinKonecny ​​Il recente thread di C ++ TS aderisce all'approccio che hai citato?
Nikos,

Quindi, alla fine, un moderno linguaggio di programmazione avrebbe bisogno sia delle Coroutine / Fibre per utilizzare in modo efficiente un singolo core della CPU, ad esempio per operazioni pesanti che non richiedono calcoli come IO e Thread, per parallelizzare le operazioni ad alta intensità di CPU su molti core per ottenere velocità, giusto?
Mahatma_Fatal_Error

19

In una parola: prelazione. Le coroutine si comportano come giocolieri che continuano a scambiarsi punti ben provati. I thread (thread veri) possono essere interrotti in quasi tutti i punti e quindi ripresi in seguito. Ovviamente, questo porta con sé ogni sorta di problemi di conflitto di risorse, quindi il famigerato GIL di Python - Global Interpreter Lock.

Molte implementazioni di thread sono in realtà più simili a coroutine.


9

Dipende dalla lingua che stai usando. Ad esempio in Lua sono la stessa cosa (viene chiamato il tipo variabile di un coroutine thread).

Di solito però le coroutine implementano la resa volontaria laddove (tu) il programmatore decidi dove yield, ad esempio, dare il controllo a un'altra routine.

I thread invece sono gestiti (arrestati e avviati) automaticamente dal sistema operativo e possono anche essere eseguiti contemporaneamente su CPU multicore.


0

12 anni di ritardo alla discussione, ma un coroutine ha la spiegazione nel nome. La coroutine può essere scomposta in Co e Routine.

Una routine in questo contesto è solo una sequenza di operazioni / azioni e eseguendo / elaborando una routine la sequenza di operazioni viene eseguita una ad una nello stesso identico ordine specificato.

Co sta per cooperazione. A una routine comune viene chiesto (o meglio ci si aspetta che sia) di sospendere volontariamente la sua esecuzione per dare ad altre co-routine la possibilità di eseguire anche. Quindi una co-routine riguarda la condivisione delle risorse della CPU (volentieri) in modo che gli altri possano usare la stessa risorsa utilizzata da loro stessi.

D'altra parte un thread non ha bisogno di sospenderne l'esecuzione. La sospensione è completamente trasparente per il thread e il thread viene forzato dall'hardware sottostante a sospendersi. Viene anche fatto in modo tale che sia per lo più trasparente al thread in quanto non viene notificato e il suo stato non viene modificato ma salvato e successivamente ripristinato quando il thread può continuare.

Una cosa che non è vera, che le co-routine non possono essere eseguite contemporaneamente e le condizioni di gara non possono verificarsi. Dipende dal sistema su cui sono in esecuzione le routine condivise ed è facile immaginare immagini condivise.

Non importa come le co-routine si sospendano. Indietro in Windows 3.1 int 03 è stato inserito in tutti i programmi (o doveva essere inserito) e in C # aggiungiamo rendimento.

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.