golang scala go

scala - kotlin vs golang performance



¿Los actores de Scala son similares a las corotinas de Go? (4)

Si quisiera portar una biblioteca de Go que usa Goroutines, ¿sería Scala una buena opción porque su bandeja de entrada / akka es similar en naturaleza a las corutinas?


Estas son respuestas excelentes y exhaustivas. Pero para una manera simple de verlo, aquí está mi punto de vista. Los goroutines son una simple abstracción de Actores. Los actores son solo un caso de uso más específico de Goroutines.

Puedes implementar Actores usando Goroutines creando el Goroutine a un lado de un Canal. Al decidir que el canal es "propiedad" de ese Goroutine, estás diciendo que solo eso consumirá Goroutine. Tu Goroutine simplemente ejecuta un bucle de correspondencia de mensaje de entrada en ese canal. A continuación, simplemente puede pasar el Canal como la "dirección" de su "Actor" (Goroutine).

Pero como Goroutines es una abstracción, un diseño más general que los actores, Goroutines se puede utilizar para muchas más tareas y diseños que los actores.

Sin embargo, una desventaja es que, dado que los actores son un caso más específico, las implementaciones de actores como Erlang pueden optimizarlos mejor (recursión ferroviaria en el bucle de la bandeja de entrada) y pueden proporcionar otras funciones incorporadas más fácilmente (actores múltiples y de máquina) .


Hay dos preguntas aquí:

  • ¿Es Scala una buena opción para portar goroutines ?

Esta es una pregunta fácil, ya que Scala es un lenguaje de propósito general, que no es peor o mejor que muchos otros que puede elegir para "portar goroutines".

Por supuesto, hay muchas opiniones sobre por qué Scala es mejor o peor como idioma (por ejemplo, here está el mío), pero estas son solo opiniones, y no dejes que te detengan. Como Scala es de propósito general, todo se reduce a: todo lo que puedes hacer en el lenguaje X, puedes hacerlo en Scala. Si suena demasiado amplio ... ¿qué hay de las continuaciones en Java :)

  • ¿Los actores de Scala son similares a los goroutines ?

La única similitud (aparte de la nitpicking) es que ambos tienen que ver con la concurrencia y el paso de mensajes. Pero ahí es donde termina la similitud.

Dado que la respuesta de Jamie dio una buena descripción general de los actores de Scala, me centraré más en Goroutines / core.async, pero con la introducción de algunos actores modelo.

Los actores ayudan a que las cosas se distribuyan "sin preocupaciones"

Donde una pieza "libre de preocupaciones" generalmente se asocia con términos tales como: fault tolerance , resiliency , availability , etc.

Sin entrar en detalles graves sobre cómo funcionan los actores, en dos términos simples, los actores tienen que ver con:

  • Localidad : cada actor tiene una dirección / referencia que otros actores pueden usar para enviar mensajes a
  • Comportamiento : una función que se aplica / llama cuando el mensaje llega a un actor

Piense en "procesos de conversación" donde cada proceso tiene una referencia y una función a la que se llama cuando llega un mensaje.

Hay mucho más por supuesto (por ejemplo, echa un vistazo a Erlang OTP , o akka docs ), pero los dos anteriores son un buen comienzo.

Donde se pone interesante con los actores es ... implementación. Dos grandes, por el momento, son Erlang OTP y Scala AKKA. Si bien ambos pretenden resolver lo mismo, existen algunas diferencias. Veamos un par:

  • Intencionalmente no uso jerga como "transparencia referencial", "idempotencia", etc. No sirven de nada además de causar confusión, así que solo hablemos de inmutabilidad [ can''t change that concepto]. Erlang como un lenguaje es obstinado, y se inclina hacia la inmutabilidad fuerte, mientras que en Scala es demasiado fácil crear actores que cambian / mutan su estado cuando se recibe un mensaje. No se recomienda, pero la mutabilidad en Scala está justo frente a usted, y la gente lo usa.

  • Otro punto interesante del que habla Joe Armstrong es el hecho de que Scala / AKKA está limitado por la JVM, que en realidad no fue diseñada pensando en la "distribución", mientras que Erlang VM sí. Tiene que ver con muchas cosas, tales como: aislamiento de procesos, por proceso vs. toda la recolección de basura de VM, carga de clases, programación de procesos y otros.

El objetivo de lo anterior no es decir que uno sea mejor que el otro, sino que muestre que la pureza del modelo de actor como concepto depende de su implementación.

Ahora a goroutines ..

Goroutines ayudan a razonar sobre concurrencia secuencialmente

Como otras respuestas ya mencionadas, los goroutines echan raíces en la Comunicación de Procesos Secuenciales , que es un "lenguaje formal para describir patrones de interacción en sistemas concurrentes", que por definición puede significar casi cualquier cosa :)

Voy a dar ejemplos basados ​​en core.async , ya que conozco los aspectos internos de él mejor que Goroutines. Pero core.async se creó después del modelo Goroutines / CSP, por lo que no debería haber demasiadas diferencias conceptuales.

La primitiva de concurrencia principal en core.async / Goroutine es un channel . Piensa en un channel como "cola en las rocas". Este canal se usa para "pasar" mensajes. Cualquier proceso que quiera "participar en un juego" crea o obtiene una referencia a un channel y pone / recibe (p. Ej., Envía / recibe) mensajes hacia y desde él.

Aparcamiento gratuito las 24 horas

La mayor parte del trabajo que se realiza en los canales generalmente ocurre dentro de un " Goroutine " o " go block ", que " toma su cuerpo y lo examina para cualquier operación de canal." Al convertirlo en una máquina de estados, al llegar a cualquier operación de bloqueo, la máquina de estado estará ''estacionada'' y se lanzará el hilo de control real. Este enfoque es similar al utilizado en C # async. Cuando la operación de bloqueo se completa, el código se reanudará (en un hilo de grupo de hilos, o hilo único en una JS VM) "( source ).

Es mucho más fácil de transmitir con un visual. Aquí es cómo se ve una ejecución de IO bloqueada:

Puede ver que los hilos pasan mayor tiempo esperando por el trabajo. Aquí está el mismo trabajo, pero se realiza a través del enfoque "Goroutine" / "go block":

Aquí 2 hilos hicieron todo el trabajo, que 4 hilos hicieron en un enfoque de bloqueo, mientras tomaban la misma cantidad de tiempo.

La descripción anterior de kicker es: "los hilos están estacionados " cuando no tienen trabajo, lo que significa que su estado se "descarga" a una máquina de estados, y el hilo de JVM en vivo es libre de hacer otro trabajo ( source para una gran visual )

Nota : en core.async, el canal se puede usar fuera de "go block" s, que estará respaldado por un hilo JVM sin capacidad de estacionamiento: por ejemplo, si bloquea, bloquea el hilo real.

Poder de un canal Go

Otra gran cosa en "Goroutines" / "go blocks" son las operaciones que se pueden realizar en un canal. Por ejemplo, se puede crear un canal de tiempo de espera , que se cerrará en X milisegundos. O seleccione / alt! función que, cuando se utiliza junto con muchos canales, funciona como un mecanismo de sondeo "¿estás listo?" en diferentes canales. Piénselo como un selector de socket en IO sin bloqueo. ¡Aquí hay un ejemplo de usar el timeout channel de timeout channel y alt! juntos:

(defn race [q] (searching [:.yahoo :.google :.bing]) (let [t (timeout timeout-ms) start (now)] (go (alt! (GET (str "/yahoo?q=" q)) ([v] (winner :.yahoo v (took start))) (GET (str "/bing?q=" q)) ([v] (winner :.bing v (took start))) (GET (str "/google?q=" q)) ([v] (winner :.google v (took start))) t ([v] (show-timeout timeout-ms))))))

Este fragmento de código está tomado de wracer , donde envía la misma solicitud a los tres: Yahoo, Bing y Google, y devuelve un resultado del más rápido o expira (devuelve un mensaje de tiempo de espera) si no se devuelve dentro de un tiempo determinado. Clojure puede no ser su primer idioma, pero no puede estar en desacuerdo sobre qué tan secuencial se ve y se siente esta implementación de simultaneidad.

También puede fusionar / desplegar / desplegar datos desde / hacia muchos canales, asignar / reducir / filtrar / ... datos de canales y más. Los canales también son ciudadanos de primera clase: puede pasar un canal a un canal.

Ve UI Go!

Dado que core.async "go blocks" tiene esta capacidad para "aparcar" el estado de ejecución, y tiene un "aspecto y tacto" muy secuencial cuando se trata de la concurrencia, ¿qué hay de JavaScript? No hay concurrencia en JavaScript, ya que solo hay un hilo, ¿verdad? Y la forma en que se simula la concurrencia es mediante 1024 devoluciones de llamada.

Pero no tiene que ser de esta manera. El ejemplo anterior de wracer está escrito en ClojureScript que se compila en JavaScript. Sí, funcionará en el servidor con muchos hilos y / o en un navegador: el código puede permanecer igual.

Goroutines vs. core.async

De nuevo, un par de diferencias de implementación [hay más] para subrayar el hecho de que el concepto teórico no es exactamente uno a uno en la práctica:

  • En Go, se escribe un canal, en core.async no es así: por ejemplo, en core.async puedes poner mensajes de cualquier tipo en el mismo canal.
  • En Go, puedes poner cosas mutables en un canal. No es recomendable, pero puedes. En Core.async, mediante el diseño de Clojure, todas las estructuras de datos son inmutables, por lo que los datos dentro de los canales se sienten mucho más seguros para su bienestar.

Entonces, ¿cuál es el veredicto?

Espero que lo anterior arroje algo de luz sobre las diferencias entre el modelo de actor y CSP.

No para causar una guerra de llamas, sino para darle otra perspectiva de, digamos, Rich Hickey:

" Sigo sin entusiasmo acerca de los actores. Todavía unen al productor con el consumidor. Sí, uno puede emular o implementar ciertos tipos de colas con los actores (y, en particular, la gente suele hacerlo), pero como cualquier mecanismo actor ya incorpora una cola, parece evidente que las colas son más primitivas. Cabe señalar que los mecanismos de Clojure para el uso simultáneo del estado siguen siendo viables, y los canales están orientados hacia los aspectos de flujo de un sistema " . ( source )

Sin embargo, en la práctica, Whatsapp se basa en Erlang OTP, y parecía vender bastante bien.

Otra cita interesante es de Rob Pike:

"Los envíos almacenados en búfer no se confirman al emisor y pueden tomarse arbitrariamente largos. Los canales almacenados en memoria intermedia y los goroutines son muy similares al modelo de actor.

La diferencia real entre el modelo de actor y Go es que los canales son ciudadanos de primera clase. También es importante: son indirectos, como los descriptores de archivos en lugar de los nombres de archivos, lo que permite estilos de concurrencia que no se expresan tan fácilmente en el modelo de actor. También hay casos en los que lo contrario es cierto; No estoy haciendo un juicio de valor. En teoría, los modelos son equivalentes. "( source )


Moviendo algunos de mis comentarios a una respuesta. Se estaba volviendo demasiado largo: D (No para quitar las publicaciones de jamie y tolitius, ambas son respuestas muy útiles).

No es del todo cierto que puedas hacer exactamente las mismas cosas que haces con los goroutines en Akka. Los canales Go se usan a menudo como puntos de sincronización. No puedes reproducir eso directamente en Akka. En Akka, el procesamiento posterior a la sincronización debe trasladarse a un manejador separado ("esparcido" en las palabras de jamie: D). Yo diría que los patrones de diseño son diferentes. Puedes patear un goroutine con un chan , hacer algunas cosas, y luego <- esperar a que termine antes de continuar. Akka tiene una forma menos poderosa de esto con ask , pero ask no es realmente la forma de Akka OMI.

Los caracteres también se escriben, mientras que los buzones no. Es un gran OMI, y es bastante impactante para un sistema basado en Scala. Entiendo que become es difícil de implementar con mensajes mecanografiados, pero tal vez eso indica que become no es muy parecido a Scala. Podría decir eso sobre Akka en general. A menudo se siente como algo propio que corre en Scala. Los goroutines son una razón clave por la que Go existe.

No me malinterpretes; Me gusta mucho el modelo de actor, y generalmente me gusta Akka y encuentro agradable trabajar en él. También me gusta Go en general (considero que Scala es hermosa, aunque considero que Go es simplemente útil, pero es bastante útil).

Pero la tolerancia a fallas es realmente el objetivo de Akka IMO. Usted obtiene concurrencia con eso. La concurrencia es el corazón de los goroutines. La tolerancia a fallas es algo separado en Go, delegado para defer y recover , que se puede usar para implementar bastante tolerancia a fallas. La tolerancia a fallas de Akka es más formal y rica en características, pero también puede ser un poco más complicada.

Dicho todo, a pesar de tener algunas similitudes pasajeras, Akka no es un superconjunto de Go, y tienen una divergencia significativa en las características. Akka y Go son bastante diferentes en la forma en que te animan a abordar los problemas, y las cosas que son fáciles en uno, son torpes, poco prácticas, o al menos no idiomáticas en el otro. Y esos son los diferenciadores clave en cualquier sistema.

Así que volviendo a tu pregunta real: recomiendo encarecidamente repensar la interfaz Go antes de llevarla a Scala o Akka (que también son cosas muy diferentes de IMO). Asegúrate de hacerlo de la forma en que tu entorno objetivo significa hacer las cosas. Es probable que un puerto directo de una biblioteca Go complicada no encaje bien con ninguno de los entornos.


No, no lo son. Los goroutines se basan en la teoría de la comunicación de procesos secuenciales, según lo especificado por Tony Hoare en 1978. La idea es que puede haber dos procesos o hilos que actúan independientemente uno del otro pero comparten un "canal", que un proceso / hilo pone datos en y el otro proceso / hilo consume. Las implementaciones más destacadas que encontrarás son los canales de Go y core.async de Clojure, pero en este momento están limitadas al tiempo de ejecución actual y no se pueden distribuir, incluso entre dos tiempos de ejecución en el mismo recuadro físico.

CSP evolucionó para incluir un álgebra de procesos formal y estática para probar la existencia de interbloqueos en el código. Esta es una característica realmente agradable, pero ni Goroutines ni core.async admiten actualmente. Si lo hacen, será extremadamente agradable saber antes de ejecutar su código si es posible o no un punto muerto. Sin embargo, CSP no admite la tolerancia a fallas de manera significativa, por lo que usted, como desarrollador, tiene que descubrir cómo manejar las fallas que pueden ocurrir en ambos lados de los canales, y dicha lógica termina siendo esparcida por toda la aplicación.

Los actores, según lo especificado por Carl Hewitt en 1973, involucran entidades que tienen su propio buzón. Son asincrónicos por naturaleza y tienen transparencia de ubicación que abarca tiempos de ejecución y máquinas. Si tiene una referencia (Akka) o PID (Erlang) de un actor, puede enviar un mensaje. Aquí también es donde algunas personas encuentran fallas en las implementaciones basadas en Actor, en el sentido de que debe tener una referencia al otro actor para enviar un mensaje, conectando así el emisor y el receptor directamente. En el modelo de CSP, el canal es compartido y puede ser compartido por múltiples productores y consumidores. En mi experiencia, esto no ha sido un gran problema. Me gusta la idea de las referencias proxy que signifiquen que mi código no está plagado de detalles de implementación de cómo enviar el mensaje; solo envío uno, y donde quiera que se encuentre el actor, lo recibe. Si ese nodo se cae y el actor se reencarna en otro lugar, es teóricamente transparente para mí.

Los actores tienen otra característica muy agradable: tolerancia a fallas. Al organizar a los actores en una jerarquía de supervisión según la especificación OTP diseñada en Erlang, puede crear un dominio de falla en su aplicación. Al igual que las clases de valor / DTOs / como quiera llamarlas, puede modelar la falla, cómo se debe manejar y en qué nivel de la jerarquía. Esto es muy poderoso, ya que tiene muy pocas capacidades de manejo de fallas dentro de CSP.

Los actores también son un paradigma de concurrencia, donde el actor puede tener un estado mutable y una garantía de no tener acceso multiproceso al estado, a menos que el desarrollador construya un sistema basado en actores que lo introduzca accidentalmente, por ejemplo registrando al actor como oyente. para una devolución de llamada, o ir asincrónico dentro del actor a través de Futures.

Enchufe desvergonzado: estoy escribiendo un nuevo libro con el jefe del equipo de Akka, Roland Kuhn, llamado Reactive Design Patterns donde discutiremos todo esto y más. Hilos verdes, CSP, bucles de eventos, iteraciones, extensiones reactivas, actores, futuros / promesas, etc. Espere ver un MEAP en Manning a principios del próximo mes.

¡Buena suerte!