Langage de programmation moderne avec abstractions intuitives de programmation simultanée [fermé]


40

Je suis intéressé par l’apprentissage de la programmation concurrente, en mettant l’accent sur le niveau application / utilisateur (pas la programmation système). Je recherche un langage de programmation moderne de haut niveau offrant des abstractions intuitives pour l'écriture d'applications simultanées. Je veux me concentrer sur les langages qui augmentent la productivité et cachent la complexité de la programmation simultanée.

Pour donner des exemples, je ne considère pas comme une bonne option d’écrire du code multithread en C, C ++ ou Java car mon humble avis est ma productivité est réduite et leur modèle de programmation n’est pas intuitif. D'autre part, les langages qui augmentent la productivité et offrent des abstractions plus intuitives telles que Python et le module de multitraitement, Erlang, Clojure, Scala, etc. seraient de bonnes options.

Que recommanderiez-vous en fonction de votre expérience et pourquoi?

EDIT: Merci à tous pour vos réponses intéressantes. Il est difficile de conclure sans essayer, car il y a beaucoup de bons candidats: Erlang, Clojure, Scala, Groovy et peut-être Haskell. J'ai voté sur la réponse avec les arguments les plus convaincants, mais je vais essayer tous les bons candidats avant de décider lequel choisir :)


21
To give an example, I don't consider a good option writing multithreaded code in C, C++, or Java. Pourquoi? On the other hand, Python and the multiprocessing module, Erlang, Clojure, Scala, etc. are some of my options.Encore une fois, pourquoi? Développez votre question pour mieux définir ce que vous recherchez réellement.
Yannis

2
Voulez-vous donc apprendre la programmation parallèle avec tous les pièges ou voulez-vous cacher une partie de sa complexité et vous concentrer sur la productivité?
MaR

@MaR Concentrez-vous sur la productivité et cachez la complexité :)
sakisk

Notez simplement que de nombreux concepts importants sont évités (certains peuvent dire résolus) dans certaines de ces langues et que, par conséquent, C est vraiment la meilleure langue pour apprendre la concurrence. (Ou du moins cela me semble; je ne connais pas assez toutes les langues répertoriées). L'augmentation de la productivité est souvent en conflit avec un apprentissage complet.
user606723

1
@DeadMG La productivité réduite est leur problème. Je ne veux pas me concentrer sur la syntaxe du langage plutôt que sur le problème. Je ne veux certainement pas finir par me débattre avec des impasses. À titre d’exemple simple, j’aimerais utiliser des énoncés simples tels que begin transaction end transactiontout doit être sans impasse et réussir ou échouer dans son ensemble.
Sakisk

Réponses:


33

Vous devriez certainement regarder Clojure - à mon avis, c'est le meilleur langage moderne pour la programmation multicœur et il est extrêmement productif.

Attributs clés:

  • C'est un langage fonctionnel , ce qui est une aubaine pour la concurrence et votre capacité à développer en utilisant des abstractions de niveau supérieur. Il présente des structures de données persistantes totalement immuables et des séquences paresseuses qui seront familières à toute personne ayant une expérience des langages fonctionnels comme Haskell.
  • Il propose un tout nouveau système de mémoire logicielle transactionnelle permettant un accès simultané sans verrouillage à un état mutable. Rendre le code sécurisé par la concurrence est souvent aussi simple que de l'envelopper dans un bloc (dosync ....).
  • C'est un Lisp - ce qui le rend extrêmement puissant pour la métaprogrammation et la génération de code à base de macros. Cela peut apporter des avantages significatifs en termes de productivité (essai de Paul Graham - "Battre les moyennes")
  • Il s’agit d’un langage JVM . Ainsi, non seulement vous avez accès à la vaste gamme de bibliothèques et d’outils de l’écosystème Java, mais vous bénéficiez également des efforts considérables déployés en matière d’ingénierie pour faire de la JVM une plate-forme efficace pour les applications côté serveur simultanées. Sur le plan pratique, cela lui confère un avantage considérable par rapport aux langues qui ne disposent pas de ce type de fondement.
  • C'est dynamique - ce qui donne un code très concis et beaucoup de productivité. Notez toutefois que vous pouvez utiliser des indicateurs de type statique facultatifs pour améliorer les performances, si nécessaire.
  • Le langage est conçu autour d'abstractions, ce qui est un peu difficile à expliquer, mais le résultat final est que vous obtenez un ensemble de fonctionnalités relativement orthogonales que vous pouvez combiner pour résoudre vos problèmes. Un exemple serait l'abstraction de séquence, qui vous permet d'écrire du code traitant de chaque type d'objet "séquentiel" (ce qui inclut tout: listes, chaînes, tableaux Java, séquences infinies de paresseux, lignes lues dans un fichier, etc.).
  • Il y a une grande communauté - utile, perspicace, mais surtout très pragmatique - l'objectif de Clojure est généralement de "faire avancer les choses".

Quelques exemples de mini code avec une inclinaison de concurrence:

;; 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))

En particulier, il vaut la peine de regarder une ou plusieurs de ces vidéos:


21
Le but des déclarations de types statiques dans les langages fortement typés n'est pas "d'améliorer les performances si nécessaire", et je commence à en avoir assez des défenseurs de Lisp qui traquent ce vieil homme de paille. Les déclarations de type ont deux objectifs: fournir certaines garanties d'exactitude au moment de la compilation et rendre le code plus facile à lire, en particulier pour une personne autre que l'auteur original. La performance intrinsèquement meilleure que procure le typage statique n’est qu’un bonus.
Mason Wheeler

8
J'ai eu à travailler avec le code JavaScript d'un autre développeur récemment au travail, et c'est la partie la plus pénible du processus: sans types dans les arguments de la fonction, je dois parcourir l'ensemble de la base de code pour comprendre ce qu'ils sont supposés faire. être et ce qu'ils peuvent faire en fonction d'où ils sont appelés. Ce ne serait pas un problème si JavaScript avait conservé le système de types de C en plus de sa syntaxe générale.
Mason Wheeler

1
@MasonWheeler: à mon humble avis, si vous ne savez pas comment appeler une fonction sans annotations de type, c'est un problème de documentation (ou de manque de documentation). Même dans les langages à caractères de canard, tout doit généralement satisfaire à certaines contraintes de type structurel (par exemple, doit supporter des opérations arithmétiques, doit être itérable, doit être indexable, etc.). Types statiques ne seraient que des effets modestes parce qu'ils ne donneraient pas beaucoup indice sur ce que la fonction fait .
Dsimcha

2
@Mason Je n'ai jamais dit qu'il n'y avait pas d'autres avantages aux déclarations de type statique. En fait, j'aime bien les déclarations de types statiques pour les raisons que vous énoncez. Cependant, j'aime aussi les gains de productivité du typage dynamique. C'est un compromis. Si vous avez une bonne suite de tests, je trouve généralement que cela atténue un grand nombre des inconvénients du typage dynamique, à la fois pour garantir l'exactitude et pour fournir un exemple de code aidant les nouveaux arrivants à comprendre l'utilisation correcte. YMMV.
Mikera

1
@dsimcha - l'alternative à la conception autour d'abstractions serait de concevoir autour d'une implémentation concrète. Par exemple, la plupart des anciennes fonctions Lisp ne fonctionnaient que sur des listes chaînées stockées dans des cellules. Vous aviez besoin de différentes fonctions pour différentes structures de données. Dans Clojure, la fonction de bibliothèque principale fonctionne sur tout ce qui est séquentiel (comme dans la réponse).
Mikera

27

Vous pouvez essayer D. Il propose trois modèles. Je recommande soit le premier ou le second.

  1. std.concurrency . Si vous utilisez ce module pour tous vos besoins en simultanéité, une combinaison du langage et de la bibliothèque standard impose l'isolation entre les threads. Les threads communiquent principalement par le biais du passage de messages, avec une prise en charge limitée de la mémoire partagée de manière à favoriser la "sécurité d'abord" et à interdire les courses de données de bas niveau. Malheureusement, la documentation de std.concurrency doit être améliorée, mais le modèle est documenté dans un chapitre gratuit du livre de Andrei Alexandrescu, "The D Programming Language".

  2. std.parallélisme . Ce module est conçu spécifiquement pour le parallélisme multicœur plutôt que pour la simultanéité en cas général. (La simultanéité et le parallélisme ne sont pas la même chose, bien que la simultanéité soit nécessaire pour mettre en œuvre le parallélisme. ) Puisque le parallélisme est synonyme de performance, std.parallelism ne fournit aucune garantie d'isolement car il rendrait difficile l'écriture de code parallèle efficace. Cependant, il élimine de nombreux détails de bas niveau sources d'erreurs, de sorte qu'il est très difficile de s'y tromper si vous parallélisez des charges de travail que vous avez vérifiées manuellement sont mutuellement indépendantes.

  3. core.thread est un wrapper de bas niveau sur les API de threading spécifiques à un système d'exploitation. Std.concurrency et std.parallelism l'utilisent sous le capot, mais je vous recommande de ne l'utiliser que si vous écrivez votre propre bibliothèque de concurrence ou que vous trouvez un cas ridicule qui ne peut pas être fait correctement, que ce soit std.parallelism ou std. .concurrency. Personne ne devrait utiliser quelque chose d'aussi bas niveau pour le travail quotidien.


Vous devriez avoir mentionné l'immutabilité / la pureté, le stockage local des threads par défaut et les partagés qui imposent une mutation dans un ordre séquentiel. Ce sont les langages qui manquent en C / C ++ pour écrire du code concurent.
deadalnix

@deadalnix: Pour moi, la plupart d'entre eux sont des détails du modèle std.concurrency (comment l'isolation est appliquée). Je voulais garder ce post concis.
dsimcha

Eh bien en fait non. La concurence nécessite à la fois une bibliothèque et un support linguistique.
deadalnix

@deadalnix: C'est vrai, mais ils ont été mis en place en grande partie pour supporter std.concurrency.
Dsimcha

23

Erlang est certainement une excellente option, mais Go , la nouvelle langue de Google, pourrait être un peu plus pratique .

Ce n'est pas si loin des autres langues communes, il est donc généralement facile à obtenir si vous connaissez déjà d'autres langues «faciles». Beaucoup de gens le comparent à Python ou même à Lua pour ce qui est du niveau de confort de programmation.


@faif pose des questions sur le niveau application / utilisateur et non sur la programmation simultanée de systèmes. Comment Erlang correspond à cela?
Chiron

@Raynos: dépend de la communauté.
Donal Fellows

@DonalFellows votre droite, je pense que ma déclaration était trop étroite
Raynos

1
@Chiron: Erlang est un langage de programmation utilisé pour créer des applications. Généralement, les applications multitraitement. Je ne sais pas où cela se situe en tant que «système simultané de planification», je n'ai entendu parler d'aucun système d'exploitation écrit en Erlang.
Javier

1
Après avoir jeté un coup d’œil dans le didacticiel Go, je tiens à dire qu’IMHO, un langage dont la syntaxe ressemble à celle du C, qui utilise des pointeurs (limités), n’est certainement pas un langage moderne qui augmente la productivité.
Sakisk

23

Jetez un coup d'œil à la programmation parallèle de Microsoft pour .net. C'est très intuitif.

De nombreux ordinateurs personnels et stations de travail ont deux ou quatre cœurs (c’est-à-dire des processeurs) qui permettent l’exécution simultanée de plusieurs threads. Dans un avenir proche, les ordinateurs devraient avoir beaucoup plus de cœurs. Pour tirer parti du matériel d'aujourd'hui et de demain, vous pouvez paralléliser votre code afin de répartir le travail entre plusieurs processeurs. Dans le passé, la parallélisation nécessitait une manipulation à bas niveau des threads et des verrous. Visual Studio 2010 et .NET Framework 4 améliorent la prise en charge de la programmation parallèle en fournissant un nouveau runtime, de nouveaux types de bibliothèques de classes et de nouveaux outils de diagnostic. Ces fonctionnalités simplifient le développement en parallèle de sorte que vous puissiez écrire du code parallèle efficace, à grain fin et évolutif dans un idiome naturel sans avoir à travailler directement avec les threads ou le pool de threads. http://i.msdn.microsoft.com/dynimg/IC292903.png


+1 C'est exactement ce qu'il demande. Cependant, lorsque des problèmes surviennent, il sera difficile de les déboguer sans comprendre la concurrence du niveau inférieur. Sans parler du fait que prendre cela comme un débutant en C # pourrait s'avérer ... intéressant.
P.Brian.Mackey

@ P.Brian.Mackey - Je suis d'accord. Cependant, ce n'est pas rare, il ne serait pas exagéré de comparer cela à l'utilisation d'ORM quand on ne comprend pas complètement le modèle relationnel et le langage SQL ...
Otávio Décio

1
Surtout PLINQ. Bien que cela ne soit utile que pour un petit sous-ensemble de tâches, il peut être très facile à utiliser.
svick

21

Erlang et Scala ont tous les deux une concurrence basée sur les acteurs , ce que j'ai trouvé très intuitif et facile à apprendre.

Le modèle d'acteur en informatique est un modèle mathématique de calcul simultané qui traite les "acteurs" comme les primitives universelles du calcul numérique concomitant: en réponse à un message qu'il reçoit, un acteur peut prendre des décisions locales, créer plus d'acteurs, envoyer plus de messages , et déterminer comment répondre au prochain message reçu ... Il a été utilisé à la fois comme cadre pour une compréhension théorique du calcul et comme base théorique pour plusieurs implémentations pratiques de systèmes concurrents.


19

J'apprends tout sur Haskell et la lecture de cet article m'a convaincu que Haskell est une bonne option pour la programmation simultanée. Parce qu'il est purement fonctionnel (le système de types sait si une fonction effectue une entrée, une sortie ou une lecture / modification d'état global), il peut faire des choses comme la mémoire transactionnelle logicielle (très bien résumée dans l'article ci-dessus) qui se comporte de la même manière que les transactions. dans les bases de données - vous obtenez un tas de choses intéressantes comme atomicity avec seulement un peu de sucre supplémentaire. Autant que je sache, les fils Haskell sont également très légers. Outre ces fonctionnalités, le fait que Haskell soit purement fonctionnel permet d'exécuter des tâches même simples en parallèle avec à peine plus d'un mot-clé (par). la source


7

Le langage GO de Google propose des outils intéressants pour l'accès simultané. Ce serait une autre chose amusante à essayer. Voir: http://golang.org/doc/effective_go.html#concurrency et en lire un peu pour des exemples.

La programmation simultanée est un sujet important et ne laisse de la place que pour certains faits saillants de Go.

La programmation simultanée dans de nombreux environnements est rendue difficile par les subtilités requises pour implémenter un accès correct aux variables partagées. Go encourage une approche différente dans laquelle les valeurs partagées sont transmises sur les canaux et, en fait, jamais activement partagées par des threads d'exécution distincts. Un seul goroutine a accès à la valeur à un moment donné. Les courses de données ne peuvent pas se produire, par conception. Pour encourager cette façon de penser, nous l’avons réduite à un slogan:

Ne communiquez pas en partageant la mémoire; au lieu de cela, partagez la mémoire en communiquant.

Cette approche peut être prise trop loin. Les comptes de référence peuvent être mieux réalisés en plaçant un mutex autour d'une variable entière, par exemple. Mais en tant qu'approche de haut niveau, l'utilisation de canaux pour contrôler l'accès facilite la rédaction de programmes clairs et corrects.

Une façon de penser à ce modèle est de considérer un programme typique à un seul thread s'exécutant sur un processeur. Il n'a pas besoin de primitives de synchronisation. Maintenant, lancez un autre exemple. il n'a pas non plus besoin de synchronisation. Maintenant, laissez ces deux communiquer. si la communication est le synchroniseur, il n'y a toujours pas besoin d'une autre synchronisation. Les pipelines Unix, par exemple, correspondent parfaitement à ce modèle. Bien que l'approche de Go en matière de simultanéité trouve son origine dans les processus de communication séquentielle (CSP) de Hoare, elle peut également être considérée comme une généralisation des types de tuyaux Unix ...


6

Dans la prochaine version, C # le rend encore plus facile que ce que montre le diagramme. Il y a deux nouveaux mots-clés Async et Await.

Async est utilisé comme modificateur de fonction et dit "cette opération effectue son travail sur un autre thread.

Await est utilisé dans une fonction Async, et c'est là que la magie se produit. En principe, Await indique au compilateur d'exécuter l'opération en suivant le mot clé dans un thread séparé et d'attendre les résultats. N'importe quel code après l'appel en attente est exécuté après l'opération.

AUSSI, l'opération se synchronise sur le thread appelant (ainsi, si vous effectuez une opération asynchrone en réponse à un clic de bouton, vous n'avez pas besoin de poster manuellement sur le thread d'interface utilisateur). Deux petits mots-clés et vous obtenez beaucoup de pouvoir d'accès simultané. Lire la suite ici


Notez que tout compilateur OS C # correct supporte déjà C # 5, async et attend.
Raynos

En principe, Await indique au compilateur d'exécuter l'opération en suivant le mot clé dans un thread séparé et d'attendre les résultats. Je me demande si cette réponse est correcte - attendre async ne concerne pas les threads. Cet article explique est agréable: Il n'y a pas de fil
sventevit

Bon point. Je suppose que j'en parlais trop simplement. Ce qui se passe réellement, c’est une "continuation" qui est souscrite à l’événement de la tâche en cours d’attente. Et oui, certaines opérations d’E / S et thread.sleep () (qui répondent fondamentalement à une interruption d’horloge) n’ont pas de thread. mais qu'en est-il des tâches manuellement faites qui ne font pas I / O comme, disons, nous avons fait une calculatrice de Fibonacci en attente? Techniquement, l'article est juste "Il n'y a pas de fil" mais en réalité il n'y en a jamais eu, c'était toujours un concept que nous utilisions pour cacher les détails de ce que le système d'exploitation faisait pour nous.
Michael Brown

6

Je recommanderais toujours le C ++. Il est plus que capable des abstractions nécessaires pour écrire du code concurrent décent. La probabilité écrasante est que vous disposiez simplement d'une médiocre bibliothèque pour effectuer le travail, car les bonnes bibliothèques pour effectuer le travail sont relativement nouvelles et, de fait, les connaissances relatives à l'utilisation de C ++ ne sont pas tout à fait communes. Le TBB d’Intel n’existe que depuis quelques années et le PPL de Microsoft n’est disponible que depuis l’année dernière.

Si vous utilisez quelque chose comme TBB ou PPL, le code simultané n'est, en fait, pas tout à fait trivial à écrire, dans la mesure où la simultanéité n'est jamais triviale, mais loin d'être ardue. Si vous utilisez directement pthreads ou les threads Win32, il n’est pas étonnant que vous n’aimiez pas ça, vous écrivez pratiquement en assembleur avec de telles fonctions. Mais avec la PPL, vous parlez d'algorithmes fonctionnels standard parallélisés pour vous, de structures de données génériques pour un accès simultané, etc.


1
Découvrez Boost.Threads ou C ++ 0x std::thread(ou std::tr1::thread). C'est en fait une très bonne abstraction, IMO.
Greyfade

1
@ Greyfade: Ils n'ont absolument rien sur PPL ou TBB. boost::threadest juste un wrapper OS avec un peu de RAII. PPL et TBB sont de vrais algorithmes concurrents, des conteneurs, etc.
DeadMG

6

Un plug pour Ada est nécessaire ici, car il contient toutes les abstractions de niveau supérieur pour le parallélisme et la simultanéité. autrement connu sous le nom de tâche . En outre, OP ayant demandé intuitif (critère subjectif!), Je pense qu'une approche différente du monde centré sur Java pourrait être appréciée.


5

Je suggérerais Groovy / Java / GPars si vous pouvez utiliser une machine virtuelle Java, car elle permet aux acteurs, aux flux de données, aux processus de communication séquentiels (CSP), au parallélisme des données, à la mémoire logicielle transactionnelle (STM), aux agents, ... Il existe de nombreux modèles de concurrence et de parallélisme de haut niveau, chacun ayant ses «points faibles» différents. Vous ne voulez pas utiliser un modèle qui ne correspond pas à la solution d'un problème que vous essayez de construire. Les langages et les frameworks avec un seul modèle vous obligent à pirater des algorithmes.

Bien sûr, je pourrais être perçu comme partial, car je contribue à Groovy et à GPars. Par contre, je travaille avec CSP et Python, cf. Python-CSP.

Un autre point est que la question initiale concerne l'apprentissage, pas l'écriture d'un système de production. Ainsi, la combinaison Groovy / Java / GPars est un bon moyen d’apprentissage, même si le travail de production éventuel est effectué en C ++ en utilisant quelque chose comme Just :: Thread Pro ou TBB plutôt qu’en étant basé sur la JVM.

(Certains liens URL parfaitement raisonnables ont dû être supprimés en raison de la panique suscitée par le spamming par le site hôte.)


Russel, si tu veux, tu peux me dire ce que tu veux dans la salle de chat et je les ajouterai pour toi: chat.stackexchange.com/rooms/21/programmers
Dan McGrath

4

Qu'en est-il de Clojure? Vous pouvez utiliser Swing par exemple, mais en profitant de la fonction de programmation simultanée Clojure? Clojure a une très bonne intégration Java.

Aussi, avez-vous envisagé Java 7 Fork / Join framework ?


2

Vous voudrez peut-être aussi consulter Groovy et la bibliothèque GPars . GPars BTW est quelque peu similaire à .NET Parallel Extension mentionné dans une autre réponse, mais la syntaxe flexible de Groovys permet une meilleure lecture dans certaines circonstances.


0

Scala a été mentionné à plusieurs reprises dans les questions et les réponses, mais je n'ai vu aucune référence à Akka, qui est une implémentation d'acteur pouvant être utilisée à la fois avec Scala et Java.


Quel est le problème avec cette réponse? Aucune autre réponse ne mentionne akka, et akka implémente une abstraction de haut niveau pour la programmation simultanée.
Giorgio

-1

Je pense que cela dépend de ce que vous construisez. Des applications de bureau ou un serveur? J'ai entendu dire que node.js (mais que je n'ai pas d'expérience personnelle) est excellent pour la programmation simultanée de serveurs (à la fois en termes d'écriture de code et de performances). Si je voulais écrire une nouvelle application serveur, je le ferais probablement. Pas sûr des applications de bureau ... J'ai écrit pas mal de choses en C # et certains outils cachent bien la complexité, bien que dans d'autres cas, vous devez vous en occuper de front.


-1

Je peux me faire mal à la tête, mais avez-vous lu le chapitre 7 de TAOUP ? La section à laquelle je pense en particulier concerne les threads par rapport aux processus. J'ai constaté que le concept de traitement simultané faisait penser la plupart des gens aux threads, mais je n'ai jamais vu de cas où un thread est plus facile et plus rapide à utiliser que de générer un processus enfant.

Vous communiquez tous les détails de la gestion de la concurrence aux personnes intelligentes qui ont créé votre système d'exploitation. Il existe déjà de nombreuses méthodes de communication en place et vous n'avez pas à vous soucier du verrouillage des ressources partagées. Fondamentalement, les threads sont un hack d’efficacité, qui relève de la règle de l’optimisation. N'optimisez pas si vous n'avez pas testé la nécessité.

Recherchez une bonne bibliothèque de sous-processus, telle que envoyé pour python . Ou vous pouvez simplement écrire plusieurs programmes séparés en C et écrire un autre programme "maître" pour utiliser fork et pipe pour générer et communiquer avec les sous-processus.


3
C’est l’opposé de ce que veut explicitement OP: créer un processus est aussi simple que créer un thread manuellement. OP s'intéresse aux abstractions de haut niveau de la concurrence.
Konrad Rudolph
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.