Lenguaje de programación moderno con abstracciones intuitivas de programación concurrente [cerrado]

40

Estoy interesado en aprender programación concurrente, centrándome en el nivel de aplicación / usuario (no en la programación del sistema). Estoy buscando un lenguaje de programación moderno de alto nivel que proporcione abstracciones intuitivas para escribir aplicaciones concurrentes. Quiero centrarme en los lenguajes que aumentan la productividad y ocultan la complejidad de la programación concurrente.

Para dar algunos ejemplos, no considero una buena opción escribir código multiproceso en C, C ++ o Java porque, en mi humilde opinión, mi productividad se reduce y su modelo de programación no es intuitivo. Por otro lado, los lenguajes que aumentan la productividad y ofrecen abstracciones más intuitivas como Python y el módulo de multiprocesamiento, Erlang, Clojure, Scala, etc. serían buenas opciones.

¿Qué recomendarías según tu experiencia y por qué?

EDITAR: Gracias a todos por sus interesantes respuestas. Es difícil llegar a una conclusión sin intentarlo, ya que hay muchos buenos candidatos: Erlang, Clojure, Scala, Groovy y quizás Haskell. Voté la respuesta con los argumentos más convincentes, pero probaré todos los buenos candidatos antes de decidir cuál elegir :)

faif
fuente
21
To give an example, I don't consider a good option writing multithreaded code in C, C++, or Java. ¿Por qué? On the other hand, Python and the multiprocessing module, Erlang, Clojure, Scala, etc. are some of my options.De nuevo por qué? Expande tu pregunta para definir mejor lo que realmente estás buscando.
Yannis
2
Entonces, ¿quieres aprender programación paralela con todas las trampas o quieres ocultar algo de su complejidad y enfocarte en la productividad?
Marzo
@MaR Céntrese en la productividad y oculte la complejidad :)
sakisk
Solo tenga en cuenta que se evitan muchos conceptos importantes (algunos pueden decir resueltos) en algunos de estos lenguajes y, por lo tanto, C es realmente el mejor lenguaje para aprender concurrencia. (O al menos eso me parece; no sé lo suficiente sobre todos los idiomas enumerados). El aumento de la productividad a menudo entra en conflicto con el aprendizaje integral.
user606723
1
@DeadMG La disminución de la productividad es su problema. No quiero centrarme en la sintaxis del lenguaje en lugar del problema. Definitivamente no quiero terminar luchando con puntos muertos. Como un ejemplo simple, quiero usar declaraciones simples como begin transaction end transactiony todo lo que contenga debe estar libre de puntos muertos y tener éxito o fallar en su conjunto.
sakisk

Respuestas:

33

Seguramente debería mirar Clojure : en mi opinión, es el mejor lenguaje moderno para la programación multinúcleo y es extremadamente productivo.

Atributos claves:

  • Es un lenguaje funcional , que es una bendición tanto para la concurrencia como para su capacidad de desarrollar usando abstracciones de nivel superior. Cuenta con estructuras de datos persistentes totalmente inmutables y secuencias perezosas que serán familiares para cualquier persona con experiencia en lenguajes funcionales como Haskell.
  • Cuenta con un sistema de memoria transaccional de software muy novedoso para acceso simultáneo sin bloqueo al estado mutable. Hacer que el código sea seguro para la concurrencia es a menudo tan simple como envolverlo en un bloque (dosync ....).
  • Es un Lisp , lo que lo hace extremadamente poderoso para la metaprogramación basada en macro y la generación de código. Esto puede traer importantes ventajas de productividad (ensayo de Paul Graham - "Batir los promedios")
  • Es un lenguaje JVM , por lo que no solo obtiene acceso a la gran variedad de bibliotecas y herramientas en el ecosistema de Java, sino que también se beneficia del enorme esfuerzo de ingeniería que se ha hecho para hacer de JVM una plataforma efectiva para aplicaciones concurrentes del lado del servidor. Para fines prácticos, esto le da una gran ventaja sobre los idiomas que no tienen este tipo de base sobre la cual construir.
  • Es dinámico , lo que da como resultado un código muy conciso y mucha productividad. Sin embargo, tenga en cuenta que puede usar sugerencias de tipo estático opcionales para el rendimiento si es necesario.
  • El lenguaje está diseñado en torno a abstracciones que es algo difícil de explicar, pero el efecto neto es que obtienes un conjunto de características relativamente ortogonales que puedes combinar para resolver tus problemas. Un ejemplo sería la abstracción de secuencia, que le permite escribir código que trata con cada tipo de objeto "secuencial" (que incluye todo, desde listas, cadenas, matrices de Java, secuencias perezosas infinitas, líneas que se leen desde un archivo, etc.)
  • Hay una gran comunidad , útil, perspicaz pero, lo más importante, muy pragmática: el enfoque en Clojure generalmente está en "hacer las cosas".

Algunos ejemplos de mini códigos con una inclinación de concurrencia:

;; 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 particular, vale la pena ver uno o más de estos videos:

mikera
fuente
21
El propósito de las declaraciones de tipo estático en lenguajes fuertemente tipados no es "mejorar el rendimiento donde sea necesario", y me estoy cansando de que los defensores de Lisp troten a ese viejo hombre de paja. Las declaraciones de tipo tienen dos propósitos: proporcionar ciertas garantías de corrección en tiempo de compilación y hacer que el código sea más fácil de leer, especialmente para alguien que no sea el autor original. El rendimiento inherentemente mejor que proporciona la escritura estática es solo una ventaja.
Mason Wheeler
8
Tuve que trabajar con el código JavaScript de otro desarrollador últimamente en el trabajo, y esa es la parte más dolorosa del proceso: sin tipos en los argumentos de la función, tengo que buscar en toda la base de código para descubrir qué se supone que deben hacer. ser y lo que pueden hacer en función de dónde se les llama. Esto no sería un problema si JavaScript hubiera retenido el sistema de tipos de C además de su sintaxis general.
Mason Wheeler
1
@MasonWheeler: en mi humilde opinión, si no puede averiguar cómo llamar a una función sin anotaciones de tipo, es un problema con la documentación (o falta de ella). Incluso en lenguajes de tipo pato, todo tiene que cumplir algunas restricciones de tipo estructural (por ejemplo, debe admitir operaciones aritméticas, debe ser iterable, debe ser indexable, etc.). Tipos estáticos sólo ayudarán mínimamente porque no quisieron dar mucha pista acerca de cuál es la función que hace .
dsimcha
2
@Mason Nunca dije que no hubiera otras ventajas para las declaraciones de tipo estático. De hecho, me gustan las declaraciones de tipo estático por exactamente las razones que usted indica. Sin embargo, también me gustan las ganancias de productividad de la escritura dinámica. Es una compensación. Si tiene un buen conjunto de pruebas, generalmente encuentro que esto mitiga muchas de las desventajas del tipeo dinámico, tanto en términos de garantizar la corrección como de proporcionar un código de ejemplo para ayudar a los recién llegados a comprender el uso correcto. YMMV.
mikera el
1
@dsimcha: la alternativa al diseño en torno a abstracciones sería diseñar en torno a una implementación concreta. Por ejemplo, la mayoría de las funciones antiguas de Lisp solo funcionaban en listas vinculadas almacenadas en celdas de contras. Necesitabas diferentes funciones para diferentes estructuras de datos. En Clojure, la función de biblioteca principal funciona en cualquier cosa secuencial (como en la respuesta).
mikera
27

Puedes probar D. Ofrece tres modelos. Recomiendo el primero o el segundo.

  1. std.concurrency . Si usa este módulo para todas sus necesidades de concurrencia, entonces una combinación del idioma y la biblioteca estándar impone el aislamiento entre hilos. Los subprocesos se comunican principalmente a través del envío de mensajes, con compatibilidad limitada para la memoria compartida de una manera que favorece la "seguridad primero" y no permite las carreras de datos de bajo nivel. Desafortunadamente, la documentación de std.concurrency necesita mejoras, pero el modelo está documentado en un capítulo gratuito del libro de Andrei Alexandrescu, "The D Programming Language".

  2. Paralelismo estándar . Este módulo está diseñado específicamente para el paralelismo multinúcleo en lugar de la concurrencia de casos generales. (La concurrencia y el paralelismo no son lo mismo, aunque la concurrencia es necesaria para implementar el paralelismo ) . Dado que todo el punto del paralelismo es el rendimiento, std.parallelism no ofrece garantías de aislamiento porque dificultarían la escritura de código paralelo eficiente. Sin embargo, abstrae muchos detalles de bajo nivel propensos a errores, por lo que es muy difícil arruinarlo si está paralelizando cargas de trabajo que ha verificado manualmente que son mutuamente independientes.

  3. core.thread es un contenedor de bajo nivel sobre las API de subprocesos específicas del sistema operativo. Tanto std.concurrency como std.parallelism lo usan debajo del capó, pero solo recomendaría usarlo si está escribiendo su propia biblioteca de concurrencia o encuentra algún caso de esquina ridículo que no se puede hacer bien en std.parallelism o std .concurrencia. Nadie debería usar algo de este nivel bajo para el trabajo diario.

dsimcha
fuente
Debería haber mencionado la inmutabilidad / pureza, el almacenamiento local de subprocesos de forma predeterminada y los compartidos que imponen la mutación en orden secuencial. Estos son idiomas que faltan en C / C ++ para escribir código concienzudo.
deadalnix
@deadalnix: Para mí, la mayoría de ellos son detalles del modelo estándar de monedas (cómo se aplica el aislamiento). Quería mantener esta publicación concisa.
dsimcha
Bueno, en realidad no. La seguridad requiere tanto la biblioteca como el soporte de idiomas.
deadalnix
@deadalnix: Correcto, pero se implementaron en gran medida para admitir std.concurrency.
dsimcha
23

Erlang es definitivamente una gran opción, pero algo un poco más práctico podría ser Go , el nuevo lenguaje de Google.

No está tan lejos de otros idiomas comunes, por lo que generalmente es fácil de obtener si ya conoce otros idiomas 'fáciles'. Muchas personas lo comparan con Python o incluso con Lua en términos de cuán "cómodo" es programar.

Javier
fuente
@faif pregunta sobre el nivel de aplicación / usuario, no sobre la programación concurrente de sistemas. ¿Cómo encaja esto Erlang?
Quirón el
@Raynos: Depende de la comunidad.
Donal Fellows
@DonalFellows tiene razón, creo que mi declaración fue demasiado limitada
Raynos
1
@Chiron: Erlang es un lenguaje de programación, se usa para crear aplicaciones. Típicamente, aplicaciones de multiprocesamiento. No sé dónde encaja como 'programación simultánea de sistemas', no he oído hablar de ningún sistema operativo escrito en Erlang.
Javier
1
Después de echar un vistazo rápido en el tutorial Go, quiero decir que, en mi humilde opinión, un lenguaje con una sintaxis tipo C que utiliza punteros (limitados) definitivamente no es un lenguaje moderno que aumente la productividad.
sakisk
23

Eche un vistazo a la programación paralela de Microsoft para .net. Es muy intuitivo.

Muchas computadoras personales y estaciones de trabajo tienen dos o cuatro núcleos (es decir, CPU) que permiten ejecutar múltiples subprocesos simultáneamente. Se espera que las computadoras en el futuro cercano tengan significativamente más núcleos. Para aprovechar el hardware de hoy y de mañana, puede paralelizar su código para distribuir el trabajo en múltiples procesadores. En el pasado, la paralelización requería una manipulación de bajo nivel de hilos y bloqueos. Visual Studio 2010 y .NET Framework 4 mejoran la compatibilidad con la programación paralela al proporcionar un nuevo tiempo de ejecución, nuevos tipos de bibliotecas de clases y nuevas herramientas de diagnóstico. Estas características simplifican el desarrollo paralelo para que pueda escribir código paralelo eficiente, detallado y escalable en un idioma natural sin tener que trabajar directamente con hilos o el grupo de hilos. http://i.msdn.microsoft.com/dynimg/IC292903.png

A ----------------------- -----
fuente
+1 Esto es exactamente lo que está pidiendo. Sin embargo, cuando surgen problemas, será difícil depurarlos sin comprender la concurrencia en un nivel inferior. Sin mencionar, asumir esto como un principiante de C # podría resultar ... interesante.
P.Brian.Mackey
@ P.Brian.Mackey - Estoy de acuerdo. Sin embargo, eso no es raro, no sería una exageración comparar esto con el uso de ORM cuando uno no comprende completamente el modelo relacional y SQL ...
Otávio Décio
1
Especialmente PLINQ. Si bien es útil solo para un pequeño subconjunto de tareas, puede ser muy fácil de usar.
svick
21

Tanto Erlang como Scala tienen concurrencia basada en actores , lo que me pareció muy intuitivo y fácil de aprender.

El modelo de actor en informática es un modelo matemático de computación concurrente que trata a los "actores" como las primitivas universales de la computación digital concurrente: en respuesta a un mensaje que recibe, un actor puede tomar decisiones locales, crear más actores, enviar más mensajes y determinar cómo responder al siguiente mensaje recibido ... Se ha utilizado tanto como marco para una comprensión teórica de la computación como como la base teórica para varias implementaciones prácticas de sistemas concurrentes.

TMN
fuente
19

Estoy aprendiendo sobre Haskell en este momento, y leer este artículo me ha convencido de que Haskell es una buena opción para la programación concurrente. Debido a que es puramente funcional (el sistema de tipos sabe si una función realiza alguna entrada, salida o lectura / modificación del estado global), puede hacer cosas como la Memoria transaccional de software (resumida muy bien en el documento anterior) que se comporta de manera similar a las transacciones en bases de datos: obtienes un montón de cosas buenas como la atomicidad con solo un poco de azúcar extra. AFAIK, los hilos Haskell también son muy livianos. Además de estas cosas, el hecho de que Haskell sea puramente funcional permite que incluso tareas simples se ejecuten en paralelo con poco más que una sola palabra clave (par). fuente

AlexWebr
fuente
7

El lenguaje GO de Google tiene algunas herramientas interesantes para la concurrencia, que sería otra cosa divertida de probar. Consulte: http://golang.org/doc/effective_go.html#concurrency y lea un poco para ver ejemplos.

La programación concurrente es un tema importante y aquí solo hay espacio para algunos aspectos destacados específicos de Go.

La programación concurrente en muchos entornos se dificulta por las sutilezas necesarias para implementar el acceso correcto a las variables compartidas. Go fomenta un enfoque diferente en el que los valores compartidos se transmiten en los canales y, de hecho, nunca se comparten activamente por hilos de ejecución separados. Solo una gorutina tiene acceso al valor en un momento dado. Las carreras de datos no pueden ocurrir, por diseño. Para fomentar esta forma de pensar, la hemos reducido a un eslogan:

No se comunique compartiendo memoria; en cambio, comparta memoria comunicándose.

Este enfoque puede llevarse demasiado lejos. Los recuentos de referencia se pueden hacer mejor colocando un mutex alrededor de una variable entera, por ejemplo. Pero como enfoque de alto nivel, el uso de canales para controlar el acceso hace que sea más fácil escribir programas claros y correctos.

Una forma de pensar en este modelo es considerar un programa típico de subproceso único que se ejecuta en una CPU. No tiene necesidad de primitivas de sincronización. Ahora ejecute otra instancia de este tipo; tampoco necesita sincronización. Ahora deja que esos dos se comuniquen; Si la comunicación es el sincronizador, todavía no hay necesidad de otra sincronización. Las tuberías Unix, por ejemplo, se ajustan perfectamente a este modelo. Aunque el enfoque de Go para la concurrencia se origina en los Procesos secuenciales de comunicación (CSP) de Hoare, también se puede ver como una generalización de tipo seguro de tuberías Unix ...

mosquito
fuente
6

En la próxima versión, C # lo hace aún más fácil de lo que muestra el diagrama. Hay dos nuevas palabras clave Async y Await.

Async se usa como un modificador de función y dice "esta operación realiza su trabajo en otro hilo.

Await se usa dentro de una función Async, y aquí es donde ocurre la magia. Básicamente, Await le dice al compilador que ejecute la operación siguiendo la palabra clave en un hilo separado y espere los resultados. Cualquier código después de la llamada en espera se ejecuta después de la operación.

TAMBIÉN, la operación se sincroniza con el subproceso de llamada (por lo tanto, si está realizando una operación asincrónica en respuesta a un clic en el botón, no tiene que volver a publicar manualmente en el subproceso de la interfaz de usuario). Dos pequeñas palabras clave y obtienes mucho poder de concurrencia. Lee más aquí

Michael Brown
fuente
Tenga en cuenta que cualquier compilador decente de C # de OS ya es compatible con C # 5, asíncrono y aguarde.
Raynos
Básicamente, Await le dice al compilador que ejecute la operación siguiendo la palabra clave en un hilo separado y espere los resultados. Me pregunto si esta respuesta es correcta: la espera asincrónica no se trata de hilos. Este artículo explica que es bueno: no hay hilo
sventevit
Buen punto. Supongo que estaba hablando demasiado simplemente sobre eso. Lo que realmente sucede es que se realiza una "continuación" que se suscribe al evento de que la tarea en "espera" se complete. Y sí, ciertas operaciones de E / S y thread.sleep () (que básicamente responde a una interrupción del reloj) no tienen un hilo. pero ¿qué pasa con las Tareas hechas manualmente que no hacen E / S como digamos que hicimos una calculadora de Fibonacci? Técnicamente, el artículo es correcto "No hay hilo", pero en realidad nunca lo hubo, siempre fue un concepto que usamos para ocultar los detalles de lo que el sistema operativo estaba haciendo por nosotros.
Michael Brown
6

Todavía recomiendo C ++. Es más que capaz de las abstracciones necesarias para escribir código concurrente decente. La probabilidad abrumadora es que simplemente tiene una biblioteca pobre para hacer el trabajo, ya que las buenas bibliotecas para hacer el trabajo son relativamente nuevas y, de hecho, el conocimiento para usar bien C ++ no es exactamente común. El TBB de Intel solo ha existido durante unos años, y el PPL de Microsoft solo se ha enviado desde el año pasado.

Si usa algo como TBB o PPL, entonces el código concurrente es, bueno, no exactamente trivial para escribir, en la medida en que la concurrencia nunca es trivial, pero está lejos de ser ardua. Si usa hilos pthreads o Win32 directamente, entonces no es de extrañar que no le guste, prácticamente está escribiendo en ensamblador con tales funciones. Pero con el PPL, entonces está hablando de algoritmos funcionales estándar que son paralelos para usted, estructuras de datos genéricos para acceso concurrente y ese tipo de cosas buenas.

DeadMG
fuente
1
Echa un vistazo a Boost.Threads o C ++ 0x std::thread(o std::tr1::thread). En realidad es una muy buena abstracción, en mi opinión.
greyfade
1
@greyfade: No tienen absolutamente nada en PPL o TBB. boost::threades solo un contenedor de sistema operativo con un poco de RAII. PPL y TBB son algoritmos, contenedores, etc. concurrentes reales
DeadMG
6

Aquí se necesita un enchufe para Ada , ya que tiene todas las abstracciones de nivel superior para paralelismo y concurrencia. también conocido como tasking . Además, como OP pidió intuitivo (¡un criterio subjetivo!), Creo que se podría apreciar un enfoque diferente del mundo centrado en Java.

NWS
fuente
5

Sugeriría Groovy / Java / GPars si puede estar basado en JVM ya que permite actores, flujo de datos, procesos secuenciales de comunicación (CSP), paralelismo de datos, memoria transaccional de software (STM), agentes, ... El punto aquí es que allí Hay muchos modelos de concurrencia y paralelismo de alto nivel, cada uno de los cuales tiene diferentes "puntos clave". No desea utilizar un modelo que no esté en armonía con la solución a un problema que está tratando de construir. Los lenguajes y marcos con un solo modelo lo obligan a piratear algoritmos.

Por supuesto, podría ser visto como parcial ya que soy colaborador de Groovy y GPars. Por otro lado, trabajo con CSP y Python, cf. Python-CSP.

Otro punto es que la pregunta original es sobre aprender, no sobre escribir un sistema de producción. Por lo tanto, la combinación Groovy / Java / GPars es una buena forma de aprender, incluso si el trabajo de producción final se realiza en C ++ utilizando algo como Just :: Thread Pro o TBB en lugar de estar basado en JVM.

(Algunos enlaces URL perfectamente razonables tuvieron que eliminarse debido a un cierto pánico sobre el envío de spam por parte del sitio host).

Russel Winder
fuente
Russel, si quieres, puedes decirme qué quieres vincular en la sala de chat y los agregaré
Dan McGrath
4

¿Qué hay de Clojure? ¿Puede usar Swing, por ejemplo, pero disfruta de la instalación de programación concurrente de Clojure? Clojure tiene una muy buena integración de Java.

Además, ¿ha considerado el framework Java 7 Fork / Join ?

Quirón
fuente
2

También es posible que desee ver Groovy y la biblioteca GPars . GPars BTW es algo similar a .NET Parallel Extension mencionado en otra respuesta, pero la sintaxis flexible de Groovys hace que se lea mejor en algunas circunstancias.

Christian Horsdal
fuente
0

Scala se ha mencionado varias veces en las preguntas y en las respuestas, pero no he visto ninguna referencia a Akka, que es una implementación de actor que se puede usar tanto con Scala como con Java.

Giorgio
fuente
¿Qué hay de malo con esta respuesta? Ninguna otra respuesta mencionó akka, y akka implementa una abstracción de alto nivel para la programación concurrente.
Giorgio
-1

Creo que depende de lo que estés construyendo. ¿Aplicaciones de escritorio o servidor? He escuchado que node.js (pero no tengo experiencia personal) es ideal para la programación concurrente para servidores (tanto en términos de escritura de código como en rendimiento). Si quisiera escribir una nueva aplicación de servidor, probablemente lo probaría. No estoy seguro acerca de las aplicaciones de escritorio ... He escrito una buena cantidad de cosas en C # y hay algunas herramientas que ocultan la complejidad muy bien, aunque para otros casos tienes que lidiar con esto de frente.

Aerik
fuente
-1

Puede que me golpeen en la cabeza por esto, pero ¿has leído el capítulo 7 de TAOUP ? La sección en la que estoy pensando específicamente es subprocesos vs procesos. Descubrí que el concepto de procesamiento concurrente hace que la mayoría de las personas piense en hilos, sin embargo, nunca he visto una instancia en la que un hilo sea más fácil y rápido de usar que generar un proceso secundario.

Estás sacando a relucir todos los detalles del manejo de la concurrencia a los tipos inteligentes que crearon tu sistema operativo. Ya existen muchos métodos de comunicación, y no tiene que preocuparse por el bloqueo de los recursos compartidos. Básicamente, los subprocesos son un truco de eficiencia, que cae bajo la regla de optimización. No optimices si no has probado la necesidad.

Encuentre una buena biblioteca de subprocesos, como enviado para python . O simplemente podría escribir varios programas separados en C, y escribir otro programa "maestro" para usar fork y pipe para generar y comunicarse con los subprocesos.

Spencer Rathbun
fuente
3
Esto es lo contrario de lo que OP quiere explícitamente ... generar un proceso es tan bajo como generar un hilo manualmente. OP está interesado en abstracciones de concurrencia de alto nivel .
Konrad Rudolph el