Il vero multithreading è davvero necessario?

4

Quindi sì, sto creando un linguaggio di programmazione. E la lingua consente più thread. Ma tutti i thread sono sincronizzati con un blocco dell'interprete globale, il che significa che solo un thread può essere eseguito alla volta. L'unico modo per far sì che i thread si spengano è quello di dire esplicitamente al thread corrente di attendere, il che consente l'esecuzione di un altro thread.

L'elaborazione parallela è ovviamente possibile generando più processi, ma non è possibile accedere a variabili e oggetti in un processo da un altro. Tuttavia, il linguaggio ha un'interfaccia IPC abbastanza efficiente per comunicare tra processi.

La mia domanda è: ci sarebbe mai una ragione per avere thread multipli e non sincronizzati all'interno di un singolo processo (aggirando così il GIL)? Perché non inserire le istruzioni thread.wait () nelle posizioni chiave nella logica del programma (presumendo che thread.wait () non sia un hog della CPU, ovviamente)?

Capisco che alcuni altri linguaggi che usano un GIL hanno problemi di programmazione del processore ( cough Python), ma sono stati tutti risolti.

    
posta Jonathan Graef 17.12.2012 - 21:23
fonte

8 risposte

7

Dici che il parallelismo è gestito da più processi, ma mi permetto di dissentire. Il parallelismo in-process è, a seconda del programma, molto più semplice e veloce, a causa della memoria condivisa (universale e predefinita).

Quando non hai memoria condivisa, hai bisogno di un sacco di ingegnerizzazione extra (come un processo server comune, con il messaggio che passa avanti e indietro) per avere uno stato condiviso al di fuori di ciò che il SO fornisce per tutti i processi (es. file system). Ciò implica anche un sacco di spese generali per i programmi con un bel po 'di stato condiviso. Sì, è necessario il blocco della maggior parte del tempo, a condizione che non si disponga di un altro meccanismo (come STM). Ma il blocco in-process può essere molto più semplice / veloce di IPC e l'accesso ai dati è molto più semplice: lo fai semplicemente, piuttosto che fare l'IPC.

Sottintendi anche questo (dalla tua descrizione di thread.wait ) che i thread non sono preventivi, cioè dipende da ogni thread quando termina la sua porzione temporale. Due note su questo:

  • È incompatibile con alcuni usi comuni dei thread (ad esempio, eseguendo un calcolo con un timeout o in modo asincrono anche se non è stato creato per essere asincroni).
  • È diverso dai thread in molte lingue comuni, quindi aspettati che i nuovi arrivati siano confusi e aspettati un sacco di codice che semplicemente non ha mai wait .

D'altra parte, preferisco l'aumento del determinismo (se anche lo scheduler è deterministico, alcuni bug di concorrenza dovrebbero essere molto più semplici da riprodurre). È un compromesso.

    
risposta data 17.12.2012 - 21:47
fonte
3

In primo luogo, la tua domanda sembra confusa sulla relazione tra un GIL e il multithreading cooperativo. Il multithreading cooperativo è quando il thread corrente continua ad essere eseguito fino a quando non si arrende. La libreria di greenlet per python si basa su questo modello. Semplifica la codifica in molti casi perché non ti devi preoccupare degli switch di contesto tranne che in punti specifici.

Un blocco dell'interprete globale è un blocco che impedisce a più thread di eseguire codice all'interno della macchina virtuale contemporaneamente. L'esecuzione salterà comunque da una discussione all'altra senza che il thread la richieda. I tempi in cui gli switch avverranno sono limitati (esattamente come dipende dall'implementazione del linguaggio). Ma non sei proprio la semplificazione che ti offre il multitasking cooperativo.

La tua domanda sembra in realtà chiedere se puoi cavartela con il multitasking cooperativo con opzioni multi-processo. Ovviamente parlando in modo rigoroso puoi ancora fare tutto ciò che vuoi senza il pre-utilizzo del multithreading, la domanda è se sarà più facile / più efficiente con esso.

Uso il multithreading cooperativo e parallelamente elabora la parallelizzazione dei processi. Il più delle volte trovo che funzioni magnificamente e sia un approccio più semplice di quello che sarebbe necessario se dovessi provare a usare i thread. Ma penso che ci siano alcuni casi in cui questo cade.

Consideriamo alcuni esempi:

1) Thread del lavoratore e thread dell'interfaccia utente

Non è raro avere un'attività a lunga esecuzione eseguita in un'applicazione mentre viene visualizzata una barra di avanzamento. Al fine di mantenere le cose funzionanti abbiamo bisogno di eseguire eventi UI e continuare a eseguire l'attività. Normalmente, dovremmo eseguire l'attività in un thread separato. Ma se i thread sono cooperativi, questo non funzionerà perché l'attività normalmente non avrà alcun motivo per ritardare se stessa.

Quindi cosa possiamo fare?

  1. In alcuni casi, ci sono stati punti di pausa naturali nel task di lunga durata. Possono esserci file I / O, chiamate database, letture socket. Tutti questi naturalmente bloccano, e se la tua lingua automaticamente filtra le attese in questi punti, molte attività di lunga durata possono produrre naturalmente.
  2. L'attività potrebbe essere spostata in un altro processo. Ma per alcuni compiti, ci vorrà un grande sforzo. Potrei dover spedire molti dati al sottoprocesso e quindi lasciarlo elaborare e spedire i dati.
  3. Potresti introdurre chiamate esplicite sul rendimento del thread. Lo svantaggio qui è che stai facendo qualcosa manualmente che gli altri linguaggi fanno automaticamente.

2) Serve molte richieste

Un sistema come un database o un server web potrebbe dover servire richieste provenienti da diversi sistemi esterni. In tal modo dovrà navigare all'interno delle strutture di dati in memoria e più richieste potrebbero richiedere le stesse strutture di dati. In genere, è possibile implementarlo utilizzando più thread e utilizzando i blocchi sulla struttura dati per assicurarsi che nessuno lo cambi mentre viene letto.

Finché abbiamo solo un core operativo, il multithreading cooperativo funziona davvero alla grande. Ma quando hai più core, non puoi approfittarne in questo modo. Potresti introdurre più processi. Ma il problema è che non si può davvero condividere le strutture dei dati in memoria così bene. Sono sicuro che puoi aggirarlo con le tecniche IPC, ma penso che sarà sempre imbarazzante rispetto al modello di serrature.

    
risposta data 17.12.2012 - 22:23
fonte
2

Ho progettato un certo numero di lingue e mi rendo conto che esistono diversi requisiti per i thread o il parallelismo.

Uno è prestazioni non elaborate. Se un programma è vincolato alla CPU e c'è un modo per parallelizzarlo su più core, è chiaro che lo si vorrebbe.

L'altro, e molto più comune, nella mia esperienza, è di semplificare la rappresentazione di sequenze di attività in cui quelle sequenze sono, in un certo senso, indipendenti l'una dall'altra.

Il caso ovvio è ascoltare su più flussi di input e rispondere alle richieste su quei flussi.

Un caso in cui lavoro molto riguarda la modellizzazione farmacologica, in cui un soggetto può ottenere un farmaco su un programma, un altro su un altro e avere osservazioni su un altro programma, mentre contemporaneamente deve affrontare eventi avversi e allarmi. Trattare questo come un processo puramente basato sugli eventi è possibile, ma la codifica è molto maldestra.

In questi casi, ciò che è necessario è un modo per esprimere chiaramente ciò che si sta tentando di dire, e ha poco a che fare con il vincolo della CPU e il bisogno di prestazioni. Per queste esigenze, il parallelismo non preventivo funziona meglio ed è molto meno incline alle condizioni di gara.

    
risposta data 27.12.2012 - 21:00
fonte
1

Ecco una risposta rapida: un thread per l'interfaccia utente e uno per il lavoro di elaborazione dati asincrona. Quindi sì, c'è bisogno di un vero multi-threading. Massicci calcoli paralleli sono un'altra ovvia risposta a cui hai alluso.

Da un punto di vista più ampio, perché non offri questa capacità? Molti, molti dispositivi hanno più core disponibili per gestire i processi. Perché dovresti limitare arbitrariamente la capacità di qualsiasi programma scritto nella lingua che stai creando?

    
risposta data 17.12.2012 - 21:41
fonte
1

In un mondo in cui le CPU possono avere più "core" e ogni "core" può funzionare su un thread separato, avere più thread indipendenti può essere una caratteristica utile.

In un mondo in cui "eventi" come i dati che arrivano dalla rete devono essere elaborati "adesso" e non quando un altro thread dice "Ho finito per ora", allora potrebbe essere utile avere più thread indipendenti.

È davvero un'elaborazione "parallela" se viene eseguita una sola attività alla volta?

La domanda migliore è questa: perché, perché stai facendo questo?

Se si tratta di una lingua specifica per il dominio, non dovresti preoccuparti di ciò che pensiamo, conosci meglio il dominio.

Se si tratta di un linguaggio generico, cosa lo distingue abbastanza da essere riconosciuto tra i molti già esistenti (oltre ad aggiungere questa limitazione)?

    
risposta data 17.12.2012 - 21:47
fonte
1

Se non ci sono stati mutabili condivisi, rende la vita molto più semplice perché non è necessario sincronizzarsi tra i processi. A quel punto hai un modello di attore come in Erlang.

L'altro problema che si ha è che se si sta contando sul programmatore per chiamare thread.wait () o simili, si ha la possibilità di un programma anomalo che abbatte l'intero sistema.

Ti suggerisco di dare una buona occhiata sia a Erlang che a Clojure perché entrambi hanno alcuni aspetti di ciò che stai cercando.

    
risposta data 27.12.2012 - 19:54
fonte
1

Dopo aver analizzato le risposte di altre persone, credo di capire ora perché il multithreading simultaneo è importante.

Ci sono due ragioni principali per cui un linguaggio generico dovrebbe avere un reale supporto preventivo al multithreading:

  1. A volte gli sviluppatori vogliono utilizzare thread reali nei loro programmi. 'Nuff said.

  2. I thread reali sono necessari per consentire la condivisione della memoria tra thread in esecuzione su più core del processore. Questa funzionalità è essenziale per l'ottimizzazione delle prestazioni in alcuni casi e in altri casi rende semplicemente codice più facile da scrivere.

Tuttavia, nei casi in cui la condivisione della memoria è meno importante, i programmi di multithreading cooperativo sono altrettanto facili da scrivere e molto più facili da eseguire il debug, purché lo sviluppatore comprenda come utilizzare il sistema.

Quindi la risposta è: Sì, il vero multithreading è davvero necessario in alcuni casi, ma solo per determinate ottimizzazioni delle prestazioni e accessibilità per gli sviluppatori che lo preferiscono. In caso contrario, il multithreading cooperativo ("falso") è la soluzione migliore.

Grazie a tutti i ragazzi di input, potrei non averlo capito senza di te. Includerò entrambi gli schemi di threading nella mia lingua. Vedremo quanto andrà bene.

    
risposta data 18.12.2012 - 07:22
fonte
0

questo giorno se vuoi più velocità per i calcoli devi andare simultaneamente, specialmente quando hai più core da usare

per dare un esempio: la maggior parte dei framework GUI ha un singolo thread che manipola lo schermo e gestisce gli eventi dell'utente, ma la gestione di questi eventi deve terminare rapidamente o l'intera GUI si bloccherà

se vuoi fare qualsiasi IO puoi usare async io e call back

il tuo linguaggio sembra fare affidamento sul multithreading cooperativo che richiede tutti il codice nell'applicazione per cooperare (o almeno non bloccare indefinitamente), il multithreading preventivo è relativamente più facile da codificare (con maggiore insidie però)

    
risposta data 17.12.2012 - 21:52
fonte

Leggi altre domande sui tag