Linguaggio di programmazione moderno con astrazioni di programmazione simultanee intuitive [chiuso]


40

Sono interessato all'apprendimento della programmazione concorrente, concentrandomi sul livello applicazione / utente (non sulla programmazione del sistema). Sto cercando un moderno linguaggio di programmazione di alto livello che fornisca astrazioni intuitive per la scrittura di applicazioni simultanee. Voglio concentrarmi su linguaggi che aumentano la produttività e nascondono la complessità della programmazione concorrente.

Per fare alcuni esempi, non considero una buona opzione per scrivere codice multithread in C, C ++ o Java perché IMHO la mia produttività è ridotta e il loro modello di programmazione non è intuitivo. D'altra parte, le lingue che aumentano la produttività e offrono astrazioni più intuitive come Python e il modulo multiprocessore, Erlang, Clojure, Scala, ecc. Sarebbero buone opzioni.

Cosa consiglieresti in base alla tua esperienza e perché?

EDIT: grazie a tutti per le vostre risposte interessanti. È difficile trarre una conclusione senza tentare davvero poiché ci sono molti buoni candidati: Erlang, Clojure, Scala, Groovy e forse Haskell. Ho votato la risposta con gli argomenti più convincenti, ma proverò tutti i buoni candidati prima di decidere quale scegliere :)


21
To give an example, I don't consider a good option writing multithreaded code in C, C++, or Java. Perché? On the other hand, Python and the multiprocessing module, Erlang, Clojure, Scala, etc. are some of my options.Ancora una volta, perché? Espandi la tua domanda per definire meglio ciò che stai effettivamente cercando.
yannis,

2
Quindi vuoi imparare la programmazione parallela con tutti i gotcha o vuoi nascondere parte della sua complessità e concentrarti sulla produttività?
MaR,

@MaR Concentrati sulla produttività e nascondi la complessità :)
sakisk

Basta notare che molti concetti importanti sono evitati (alcuni potrebbero dire risolti) in alcune di queste lingue e quindi C è davvero la lingua migliore per imparare la concorrenza. (O almeno mi sembra; non so abbastanza di tutte le lingue elencate). L'aumento della produttività è spesso in conflitto con l'apprendimento globale.
user606723

1
@DeadMG La produttività ridotta è il loro problema. Non voglio concentrarmi sulla sintassi della lingua anziché sul problema. Sicuramente non voglio finire alle prese con deadlock. Come semplice esempio, voglio usare semplici istruzioni come begin transaction end transactione tutto ciò che è dentro dovrebbe essere privo di deadlock e riuscire o fallire nel suo insieme.
sakisk,

Risposte:


33

Dovresti quasi sicuramente guardare Clojure : secondo me è il miglior linguaggio moderno per la programmazione multi-core ed è estremamente produttivo.

Attributi chiave:

  • È un linguaggio funzionale , che è un vantaggio sia per la concorrenza che per la tua capacità di sviluppo usando astrazioni di livello superiore. È dotato di strutture di dati persistenti completamente immutabili e sequenze pigre che saranno familiari a chiunque abbia esperienza in linguaggi funzionali come Haskell.
  • È dotato di un nuovissimo sistema di memoria transazionale del software per l'accesso simultaneo senza blocco allo stato mutabile. Rendere il codice concorrenziale sicuro è spesso semplice come racchiuderlo in un blocco (dosync ....).
  • È un Lisp , che lo rende estremamente potente per la metaprogrammazione basata su macro e la generazione di codice. Ciò può comportare significativi vantaggi in termini di produttività (saggio di Paul Graham - "Beating The Averages")
  • È un linguaggio JVM - quindi non solo accedi alla vasta gamma di librerie e strumenti nell'ecosistema Java, ma beneficerai anche dell'enorme sforzo ingegneristico che ha reso la JVM una piattaforma efficace per applicazioni simultanee sul lato server. Ai fini pratici, ciò offre un enorme vantaggio rispetto alle lingue che non hanno questo tipo di fondamento su cui basarsi.
  • È dinamico , il che si traduce in un codice molto conciso e molta produttività. Si noti tuttavia che è possibile utilizzare suggerimenti di tipo statico opzionale per le prestazioni, se necessario.
  • Il linguaggio è progettato attorno ad astrazioni che è in qualche modo difficile da spiegare, ma l'effetto netto è che ottieni una serie di caratteristiche relativamente ortogonali che puoi combinare per risolvere i tuoi problemi. Un esempio potrebbe essere l'astrazione della sequenza, che consente di scrivere codice che si occupa di ogni tipo di oggetto "sequenziale" (che include qualsiasi cosa da elenchi, stringhe, array Java, sequenze pigre infinite, linee lette da un file ecc.)
  • C'è una grande comunità - utile, perspicace ma soprattutto molto pragmatica - l'attenzione di Clojure è generalmente focalizzata sul "fare le cose".

Alcuni esempi di mini-codice con un'inclinazione della concorrenza:

;; define and launch a future to execute do-something in another thread
(def a (future (do-something)))

;; wait for the future to finish and print its return value
(println @a)

;; call two functions protected in a single STM transaction
(dosync
  (function-one)
  (function-two))

In particolare, vale la pena guardare uno o più di questi video:


21
Lo scopo delle dichiarazioni di tipo statico in linguaggi fortemente tipizzati non è quello di "migliorare le prestazioni laddove necessario" e mi sto stancando dei sostenitori di Lisp che trotterellano quel vecchio uomo di paglia. Le dichiarazioni di tipo hanno due scopi: fornire certe garanzie di correttezza in fase di compilazione e rendere più facile la lettura del codice, specialmente per persone diverse dall'autore originale. Le prestazioni intrinsecamente migliori fornite dalla tipizzazione statica sono solo un vantaggio.
Mason Wheeler,

8
Di recente ho dovuto lavorare con un codice JavaScript di un altro sviluppatore al lavoro, e questa è la parte più dolorosa del processo: senza tipi sugli argomenti della funzione, devo cercare in tutta la base di codice per capire cosa dovrebbero essere e cosa possono fare in base alla loro provenienza. Questo non sarebbe un problema se JavaScript avesse mantenuto il sistema di tipo C in aggiunta alla sua sintassi generale.
Mason Wheeler,

1
@MasonWheeler: IMHO se non riesci a capire come chiamare una funzione senza scrivere annotazioni, è un problema con la documentazione (o la sua mancanza). Anche nei linguaggi tipografici duck tutto di solito deve soddisfare alcuni vincoli di tipo strutturale (ad es. Deve supportare operazioni aritmetiche, deve essere iterabile, deve essere indicizzabile, ecc.). Tipi statici sarebbe solo aiutare minimamente perché non volevano dare molto indizio su ciò che la funzione fa .
dsimcha,

2
@Mason Non ho mai detto che non c'erano altri vantaggi nelle dichiarazioni di tipo statico. In effetti, mi piacciono le dichiarazioni di tipo statico esattamente per i motivi che affermi. Tuttavia, mi piacciono anche i guadagni di produttività della digitazione dinamica. È un compromesso. Se disponi di una buona suite di test, in genere scopro che ciò mitiga molti aspetti negativi della tipizzazione dinamica sia in termini di garantire la correttezza che di fornire un codice di esempio per aiutare i nuovi arrivati ​​a capire il corretto utilizzo. YMMV.
Mikera,

1
@dsimcha - l'alternativa alla progettazione attorno alle astrazioni sarebbe progettare attorno a un'implementazione concreta. Ad esempio, la maggior parte delle vecchie funzioni Lisp ha funzionato solo su liste collegate memorizzate in celle di contro. Avevi bisogno di diverse funzioni per diverse strutture di dati. In Clojure, la funzione di libreria principale funziona su qualsiasi sequenza (come nella risposta).
Mikera,

27

Potresti provare D. Offre tre modelli. Raccomando il primo o il secondo.

  1. std.concurrency . Se si utilizza questo modulo per tutte le esigenze di concorrenza, una combinazione della lingua e della libreria standard impone l'isolamento tra i thread. I thread comunicano principalmente tramite il passaggio di messaggi, con un supporto limitato per la memoria condivisa in un modo che favorisce la "sicurezza prima di tutto" e impedisce le corse di dati di basso livello. Sfortunatamente la documentazione di std.concurrency deve essere migliorata, ma il modello è documentato in un capitolo gratuito del libro di Andrei Alexandrescu, "Il linguaggio di programmazione D".

  2. parallelismo standard . Questo modulo è progettato specificamente per il parallelismo multicore anziché per la concorrenza di casi generali. (La concorrenza e il parallelismo non sono la stessa cosa, sebbene la concorrenza sia necessaria per implementare il parallelismo. ) Dato che l'intero punto del parallelismo è rappresentato dalle prestazioni, il parallelismo standard non garantisce garanzie di isolamento perché renderebbe difficile scrivere codice parallelo efficiente. Tuttavia, elimina molti dettagli di basso livello soggetti a errori, quindi è molto difficile sbagliare se si parallelizzano i carichi di lavoro che sono stati verificati manualmente reciprocamente indipendenti.

  3. core.thread è un wrapper di basso livello su API di threading specifiche del sistema operativo. Sia std.concurrency che std.parallelism lo usano sotto il cofano, ma consiglierei di usarlo solo se stai scrivendo la tua libreria di concorrenza o trovi qualche ridicolo caso d'angolo che non può essere fatto bene in std.parallelism o std .concorrenza. Nessuno dovrebbe usare qualcosa di così basso livello per il lavoro quotidiano.


Avresti dovuto menzionare l'immutabilità / purezza, l'archiviazione locale dei thread per impostazione predefinita e la condivisione che impongono la mutazione in ordine sequenziale. Questi sono i linguaggi che mancano in C / C ++ per scrivere codice simultaneo.
deadalnix,

@deadalnix: per me la maggior parte di questi sono dettagli del modello std.concurrency (come viene applicato l'isolamento). Volevo mantenere questo post conciso.
dsimcha,

Beh in realtà no. La coerenza richiede sia il supporto libreria che il linguaggio.
deadalnix,

@deadalnix: giusto, ma sono stati messi in atto principalmente per supportare la concorrenza std.
dsimcha,

23

Erlang è sicuramente un'ottima opzione, ma qualcosa di un po 'più pratico potrebbe essere Go , la nuova lingua di Google.

Non è così lontano da altre lingue comuni, quindi in genere è facile da ottenere se conosci già altre lingue "facili". Molte persone lo paragonano a Python o persino a Lua in termini di programmazione "comoda".


@faif chiede informazioni a livello di applicazione / utente, non di programmazione concorrente dei sistemi. Come si adatta Erlang?
Chirone,

@Raynos: dipende dalla comunità.
Donal Fellows,

@DonalFellows your right, penso che la mia affermazione fosse troppo stretta
Raynos,

1
@Chiron: Erlang è un linguaggio di programmazione, viene utilizzato per creare applicazioni. Tipicamente, applicazioni multielaborazione. Non so dove si adatti come "proramming simultaneo di sistemi", non ho sentito parlare di alcun sistema operativo scritto in Erlang.
Javier,

1
Dopo aver dato una rapida occhiata al tutorial di Go, voglio dire che IMHO un linguaggio con una sintassi di tipo C che utilizza puntatori (limitati) non è sicuramente un linguaggio moderno che aumenta la produttività.
sakisk,

23

Dai un'occhiata alla programmazione parallela di Microsoft per .net. È molto intuitivo.

Molti personal computer e workstation dispongono di due o quattro core (ovvero CPU) che consentono l'esecuzione simultanea di più thread. I computer nel prossimo futuro dovrebbero avere un numero significativamente maggiore di core. Per sfruttare l'hardware di oggi e di domani, è possibile parallelizzare il codice per distribuire il lavoro su più processori. In passato, la parallelizzazione richiedeva una manipolazione a basso livello di thread e blocchi. Visual Studio 2010 e .NET Framework 4 migliorano il supporto per la programmazione parallela fornendo un nuovo runtime, nuovi tipi di librerie di classi e nuovi strumenti diagnostici. Queste funzionalità semplificano lo sviluppo parallelo in modo da poter scrivere codice parallelo efficiente, a grana fine e scalabile in un linguaggio naturale senza dover lavorare direttamente con i thread o il pool di thread. http://i.msdn.microsoft.com/dynimg/IC292903.png


+1 Questo è esattamente quello che sta chiedendo. Tuttavia, quando si verificano problemi, sarà difficile eseguirne il debug senza una comprensione della concorrenza a un livello inferiore. Per non parlare del fatto che assumerlo come principiante in C # potrebbe rivelarsi ... interessante.
P.Brian.Mackey,

@ P.Brian.Mackey - Sono d'accordo. Tuttavia, ciò non è insolito, non sarebbe un periodo di paragone confrontarlo con l'utilizzo degli ORM quando non si comprende appieno il modello relazionale e l'SQL ...
Otávio Décio

1
Soprattutto PLINQ. Sebbene sia utile solo per un piccolo sottoinsieme di attività, può essere molto facile da usare.
svick,

21

Sia Erlang che Scala hanno una concorrenza basata sugli attori , che ho trovato molto intuitiva e facile da imparare.

Il modello di attore in informatica è un modello matematico di calcolo simultaneo che tratta gli "attori" come i primitivi universali del calcolo digitale concorrente: in risposta a un messaggio che riceve, un attore può prendere decisioni locali, creare più attori, inviare più messaggi e determinare come rispondere al prossimo messaggio ricevuto ... È stato usato sia come framework per una comprensione teorica del calcolo, sia come base teorica per diverse implementazioni pratiche di sistemi concorrenti.


19

Sto imparando a conoscere Haskell in questo momento e la lettura di questo documento mi ha convinto che Haskell è una buona opzione per la programmazione concorrente. Poiché è puramente funzionale (il sistema dei tipi sa se una funzione fa input, output o lettura / modifica dello stato globale), può fare cose come la memoria transazionale del software (riassunta molto bene nel documento sopra) che si comporta in modo simile alle transazioni nei database - ottieni un sacco di cose carine come l'atomicità con solo un po 'di zucchero in più. AFAIK, i fili Haskell sono anche molto leggeri. Oltre a queste cose, il fatto che Haskell sia puramente funzionale consente di eseguire anche attività semplici in parallelo con poco più di una singola parola chiave (par). fonte


7

La lingua GO di Google ha alcuni strumenti interessanti per la concorrenza: sarebbe un'altra cosa divertente da provare. Vedi: http://golang.org/doc/effective_go.html#concurrency e leggi un po 'per esempi.

La programmazione concorrente è un argomento di grandi dimensioni e qui c'è spazio solo per alcuni punti salienti specifici di Go.

La programmazione concorrente in molti ambienti è resa difficile dalle sottigliezze necessarie per implementare un accesso corretto alle variabili condivise. Go incoraggia un approccio diverso in cui i valori condivisi vengono trasmessi sui canali e, di fatto, mai condivisi attivamente da thread separati di esecuzione. Solo una goroutine ha accesso al valore in un dato momento. Non possono verificarsi gare di dati, in base alla progettazione. Per incoraggiare questo modo di pensare, lo abbiamo ridotto a uno slogan:

Non comunicare condividendo la memoria; invece, condividi la memoria comunicando.

Questo approccio può essere preso troppo lontano. I conteggi dei riferimenti possono essere eseguiti meglio inserendo un mutex attorno a una variabile intera, ad esempio. Ma come approccio di alto livello, l'uso dei canali per controllare l'accesso facilita la scrittura di programmi chiari e corretti.

Un modo di pensare a questo modello è quello di considerare un tipico programma a thread singolo in esecuzione su una CPU. Non ha bisogno di primitive di sincronizzazione. Ora esegui un'altra istanza del genere; anche lui non ha bisogno di sincronizzazione. Ora lascia che quei due comunichino; se la comunicazione è il sincronizzatore, non è ancora necessaria altra sincronizzazione. Le tubazioni Unix, ad esempio, si adattano perfettamente a questo modello. Sebbene l'approccio di Go alla concorrenza abbia origine nel processo di comunicazione sequenziale (CSP) di Hoare, può anche essere visto come una generalizzazione sicura dei tipi di tubi Unix ...


6

Nella prossima versione, C # lo rende ancora più semplice di quanto mostra quel diagramma. Esistono due nuove parole chiave Async e Await.

Async viene utilizzato come modificatore di funzione e dice "questa operazione esegue il suo lavoro su un altro thread.

In attesa viene utilizzato all'interno di una funzione asincrona, ed è qui che avviene la magia. Fondamentalmente In attesa di dire al compilatore di eseguire l'operazione seguendo la parola chiave in un thread separato e attendere i risultati. Qualsiasi codice dopo la chiamata in attesa viene eseguito dopo l'operazione.

ANCHE, l'operazione si sincronizza con il thread chiamante (quindi se si sta eseguendo un'operazione asincrona in risposta a un clic sul pulsante, non è necessario riportare manualmente al thread dell'interfaccia utente). Due piccole parole chiave e ottieni un sacco di potere di concorrenza. Leggi di più qui


Nota che qualsiasi compilatore C # OS decente supporta già C # 5, asincronizza e attende.
Raynos,

Fondamentalmente In attesa di dire al compilatore di eseguire l'operazione seguendo la parola chiave in un thread separato e attendere i risultati. Mi chiedo se questa risposta sia corretta: l'attesa asincrona non riguarda i thread. Questo articolo spiega è carino: non c'è discussione
sventevit

Buon punto. Immagino che ne stavo parlando troppo semplicemente. Ciò che effettivamente accade è una "continuazione" che viene effettuata per la sottoscrizione dell'evento nell'attesa "completamento". E sì, alcune operazioni di I / O e thread.sleep () (che sostanzialmente risponde a un interrupt di clock) non hanno un thread. ma per quanto riguarda le attività fatte manualmente che non richiedono I / O, diciamo che abbiamo creato un calcolatore calcolatrice fibonacci? Tecnicamente l'articolo è giusto "Non c'è discussione" ma in realtà non c'è mai stato, è sempre stato un concetto che abbiamo usato per nascondere i dettagli di ciò che il sistema operativo stava facendo per noi.
Michael Brown,

6

Consiglierei ancora C ++. È più che capace delle astrazioni necessarie per scrivere un codice concorrente decente. La stragrande probabilità è che tu abbia semplicemente una libreria scadente per fare il lavoro, dal momento che le buone librerie per fare il lavoro sono relativamente nuove e, in effetti, la conoscenza per usare bene il C ++ non è esattamente comune. Il TBB di Intel esiste da alcuni anni e il PPL di Microsoft è stato spedito solo dall'anno scorso.

Se usi qualcosa come TBB o PPL, allora il codice concorrente non è esattamente banale da scrivere, nella misura in cui la concorrenza non è mai banale, ma tutt'altro che ardua. Se usi direttamente pthreads o thread Win32, non c'è da meravigliarsi che non ti piaccia, praticamente stai scrivendo in assembler con tali funzioni. Ma con la PPL, allora stai parlando di algoritmi funzionali standard che sono parallelizzati per te, strutture di dati generici per l'accesso simultaneo e quel genere di cose buone.


1
Dai un'occhiata a Boost.Threads o C ++ 0x std::thread(o std::tr1::thread). In realtà è un'ottima astrazione, IMO.
Greyfade,

1
@greyfade: non hanno assolutamente nulla su PPL o TBB. boost::threadè solo un wrapper di sistema operativo con un po 'di RAII. PPL e TBB sono veri e propri algoritmi simultanei, contenitori, ecc.
DeadMG,

6

Qui è necessaria una spina per Ada , in quanto ha tutte le astrazioni di massimo livello per parallelismo e concorrenza. altrimenti noto come tasking . Inoltre, poiché l'OP ha richiesto un criterio intuitivo (un criterio soggettivo!), Penso che potrebbe essere apprezzato un approccio diverso al mondo incentrato sul Java.


5

Vorrei suggerire Groovy / Java / GPars se si può essere basati su JVM poiché consente attori, flusso di dati, processi sequenziali comunicanti (CSP), parallelismo dei dati, memoria transazionale del software (STM), agenti, ... Il punto qui è che lì sono molti modelli di alto livello di concorrenza e parallelismo, ognuno dei quali ha diversi "punti deboli". Non si desidera utilizzare un modello non in armonia con la soluzione di un problema che si sta tentando di costruire. Le lingue e i framework con un solo modello ti costringono alla pirateria informatica.

Ovviamente potrei essere visto di parte perché contribuisco a Groovy e GPars. D'altra parte, lavoro con CSP e Python, cfr. Python-CSP.

Un altro punto è che la domanda originale riguarda l'apprendimento, non la scrittura di un sistema di produzione. Quindi la combinazione Groovy / Java / GPars è un buon modo di apprendere anche se l'eventuale lavoro di produzione viene eseguito in C ++ usando qualcosa come Just :: Thread Pro o TBB anziché essere basato su JVM.

(Alcuni collegamenti URL perfettamente ragionevoli dovevano essere rimossi a causa del panico sullo spamming da parte del sito host.)


Russel, se vuoi, puoi dirmi cosa vuoi collegare nella chat room e li aggiungerò per te: chat.stackexchange.com/rooms/21/programmers
Dan McGrath,

4

Che dire di Clojure? Puoi usare Swing per esempio ma ti stai godendo la funzione di programmazione simultanea Clojure? Clojure ha un'integrazione Java abbastanza buona.

Inoltre, hai considerato Java 7 Fork / Join framework ?


2

Potresti anche voler dare un'occhiata a Groovy e alla libreria GPars . GPars BTW è in qualche modo simile all'estensione parallela .NET menzionata in un'altra risposta, ma la sintassi flessibile di Groovys lo rende migliore in alcune circostanze.


0

Scala è stato menzionato più volte nelle domande e nelle risposte, ma non ho visto alcun riferimento ad Akka, che è un'implementazione dell'attore che può essere utilizzata sia con Scala che con Java.


Cosa c'è di sbagliato in questa risposta? Nessun'altra risposta ha menzionato Akka e Akka implementa un'astrazione di alto livello per la programmazione concorrente.
Giorgio,

-1

Penso che dipenda da cosa stai costruendo. App desktop o server? Ho sentito che (ma non ho esperienza personale) node.js ottimo per la programmazione concorrente per server (sia in termini di scrittura del codice che di prestazioni). Se volessi scrivere una nuova app per server, probabilmente lo proverei. Non sono sicuro delle app desktop ... Ho scritto un bel po 'di roba in C # e ci sono alcuni strumenti che nascondono bene la complessità, anche se per altri casi devi affrontarla direttamente.


-1

Potrei essere colpito in testa per questo, ma hai letto il capitolo 7 di TAOUP ? La sezione a cui sto pensando in particolare è thread vs processi. Ho scoperto che il concetto di elaborazione simultanea fa pensare alla maggior parte dei thread, ma non ho mai visto un'istanza in cui un thread è più facile e veloce da usare rispetto alla generazione di un processo figlio.

Stai fornendo tutti i dettagli della gestione della concorrenza ai ragazzi intelligenti che hanno creato il tuo sistema operativo. Esistono già molti metodi di comunicazione e non devi preoccuparti del blocco delle risorse condivise. Fondamentalmente, i thread sono un hack di efficienza, che rientra nella regola di ottimizzazione. Non ottimizzare se non hai testato per necessità.

Trova una buona libreria di sottoprocessi, come l' inviato per Python . Oppure potresti semplicemente scrivere diversi programmi separati in C e scrivere un altro programma "master" per usare fork and pipe per generare e comunicare con i sottoprocessi.


3
Questo è l'opposto di ciò che OP vuole esplicitamente ... generare un processo è altrettanto basso quanto generare manualmente un thread. OP è interessato alle astrazioni di alto livello della concorrenza.
Konrad Rudolph,
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.