He estado observando la creciente visibilidad de los lenguajes de programación funcionales y las características durante un tiempo. Los miré y no vi el motivo de la apelación.
Luego, recientemente asistí a la presentación "Basics of Erlang" de Kevin Smith en Codemash .
Disfruté de la presentación y aprendí que muchos de los atributos de la programación funcional hacen que sea mucho más fácil evitar problemas de subprocesos / concurrencia. Entiendo que la falta de estado y mutabilidad hace que sea imposible que varios subprocesos alteren los mismos datos, pero Kevin dijo (si entendí correctamente) que toda la comunicación se realiza a través de mensajes y los mensajes se procesan sincrónicamente (evitando nuevamente problemas de concurrencia).
Pero he leído que Erlang se usa en aplicaciones altamente escalables (la razón por la que Ericsson lo creó en primer lugar). ¿Cómo puede ser eficiente manejar miles de solicitudes por segundo si todo se maneja como un mensaje procesado sincrónicamente? ¿No es por eso que comenzamos a avanzar hacia el procesamiento asincrónico, para que podamos aprovechar la ejecución de múltiples subprocesos de operación al mismo tiempo y lograr escalabilidad? Parece que esta arquitectura, aunque más segura, es un paso atrás en términos de escalabilidad. ¿Qué me estoy perdiendo?
Entiendo que los creadores de Erlang evitaron intencionalmente el soporte de subprocesos para evitar problemas de concurrencia, pero pensé que el subproceso múltiple era necesario para lograr la escalabilidad.
¿Cómo pueden los lenguajes de programación funcionales ser intrínsecamente seguros para subprocesos, pero aún así escalar?
fuente
Respuestas:
Un lenguaje funcional no se basa (en general) en la mutación de una variable. Debido a esto, no tenemos que proteger el "estado compartido" de una variable, porque el valor es fijo. Esto, a su vez, evita la mayoría de los saltos por los que tienen que pasar los lenguajes tradicionales para implementar un algoritmo en procesadores o máquinas.
Erlang lo lleva más allá de los lenguajes funcionales tradicionales al crear un sistema de paso de mensajes que permite que todo funcione en un sistema basado en eventos donde un fragmento de código solo se preocupa por recibir mensajes y enviar mensajes, sin preocuparse por una imagen más grande.
Lo que esto significa es que al programador (nominalmente) no le preocupa que el mensaje sea manejado en otro procesador o máquina: simplemente enviar el mensaje es lo suficientemente bueno para que continúe. Si le importa una respuesta, la esperará como otro mensaje .
El resultado final de esto es que cada fragmento es independiente de todos los demás fragmentos. Sin código compartido, sin estado compartido y todas las interacciones provenientes de un sistema de mensajes que se puede distribuir entre muchas piezas de hardware (o no).
Compare esto con un sistema tradicional: tenemos que colocar mutex y semáforos alrededor de las variables "protegidas" y la ejecución del código. Tenemos un enlace estricto en una llamada de función a través de la pila (esperando que ocurra el retorno). Todo esto crea cuellos de botella que son un problema menor en un sistema de nada compartido como Erlang.
EDITAR: También debo señalar que Erlang es asincrónico. Envías tu mensaje y tal vez / algún día llegue otro mensaje. O no.
El punto de Spencer sobre la ejecución fuera de orden también es importante y está bien respondido.
fuente
El sistema de cola de mensajes es genial porque produce efectivamente un efecto de "disparar y esperar el resultado", que es la parte sincrónica sobre la que está leyendo. Lo que hace que esto sea increíblemente asombroso es que significa que las líneas no necesitan ejecutarse secuencialmente. Considere el siguiente código:
Considere por un momento que methodWithALotOfDiskProcessing () tarda aproximadamente 2 segundos en completarse y que methodWithALotOfNetworkProcessing () tarda aproximadamente 1 segundo en completarse. En un lenguaje de procedimiento, este código tardaría unos 3 segundos en ejecutarse porque las líneas se ejecutarían secuencialmente. Estamos perdiendo el tiempo esperando a que se complete un método que pueda ejecutarse al mismo tiempo que el otro sin competir por un solo recurso. En un lenguaje funcional, las líneas de código no dictan cuándo las intentará el procesador. Un lenguaje funcional probaría algo como lo siguiente:
¿Cuan genial es eso? Al seguir adelante con el código y esperar solo cuando sea necesario, ¡hemos reducido el tiempo de espera a dos segundos automáticamente! : D Así que sí, aunque el código es síncrono, tiende a tener un significado diferente al de los lenguajes de procedimiento.
EDITAR:
Una vez que comprende este concepto junto con la publicación de Godeke, es fácil imaginar lo simple que se vuelve aprovechar múltiples procesadores, granjas de servidores, almacenes de datos redundantes y quién sabe qué más.
fuente
Es probable que esté mezclando sincrónico con secuencial .
El cuerpo de una función en erlang se procesa secuencialmente. Entonces, lo que dijo Spencer sobre este "efecto automágico" no es válido para erlang. Sin embargo, puedes modelar este comportamiento con erlang.
Por ejemplo, podría generar un proceso que calcule el número de palabras en una línea. Como tenemos varias líneas, generamos uno de esos procesos para cada línea y recibimos las respuestas para calcular una suma a partir de él.
De esa manera, generamos procesos que hacen los cálculos "pesados" (utilizando núcleos adicionales si están disponibles) y luego recopilamos los resultados.
Y así es como se ve, cuando ejecutamos esto en el shell:
fuente
La clave que permite escalar a Erlang está relacionada con la concurrencia.
Un sistema operativo proporciona simultaneidad mediante dos mecanismos:
Los procesos no comparten el estado: un proceso no puede bloquear otro por diseño.
Los subprocesos comparten el estado: un subproceso puede bloquear otro por diseño, ese es su problema.
Con Erlang, la máquina virtual utiliza un proceso del sistema operativo y la VM proporciona simultaneidad al programa Erlang no mediante el uso de subprocesos del sistema operativo, sino al proporcionar procesos Erlang, es decir, Erlang implementa su propio selector de tiempo.
Estos procesos de Erlang se comunican entre sí mediante el envío de mensajes (manejados por Erlang VM, no por el sistema operativo). Los procesos de Erlang se dirigen entre sí mediante un ID de proceso (PID) que tiene una dirección de tres partes
<<N3.N2.N1>>
:Dos procesos en la misma VM, en diferentes VM en la misma máquina o dos máquinas se comunican de la misma manera; por lo tanto, su escalado es independiente de la cantidad de máquinas físicas en las que implementa su aplicación (en la primera aproximación).
Erlang solo es seguro para subprocesos en un sentido trivial: no tiene subprocesos. (El lenguaje, es decir, la máquina virtual SMP / multi-core usa un subproceso del sistema operativo por núcleo).
fuente
Es posible que no haya entendido bien cómo funciona Erlang. El tiempo de ejecución de Erlang minimiza el cambio de contexto en una CPU, pero si hay varias CPU disponibles, todas se utilizan para procesar mensajes. No tiene "hilos" en el sentido en que los tiene en otros idiomas, pero puede tener muchos mensajes que se procesan al mismo tiempo.
fuente
Los mensajes de Erlang son puramente asincrónicos, si desea una respuesta sincrónica a su mensaje, debe codificarlo explícitamente. Lo que posiblemente se dijo fue que los mensajes en un cuadro de mensaje de proceso se procesan secuencialmente. Cualquier mensaje enviado a un proceso se coloca en ese cuadro de mensaje de proceso, y el proceso puede elegir un mensaje de ese cuadro, procesarlo y luego pasar al siguiente, en el orden que crea conveniente. Este es un acto muy secuencial y el bloque de recepción hace exactamente eso.
Parece que has mezclado sincrónico y secuencial como mencionó Chris.
fuente
Transparencia referencial: consulte http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)
fuente
En un lenguaje puramente funcional, el orden de evaluación no importa: en una aplicación de función fn (arg1, .. argn), los n argumentos se pueden evaluar en paralelo. Eso garantiza un alto nivel de paralelismo (automático).
Erlang usa un modelo de proceso en el que un proceso puede ejecutarse en la misma máquina virtual o en un procesador diferente; no hay forma de saberlo. Eso solo es posible porque los mensajes se copian entre procesos, no hay un estado compartido (mutable). El paralelismo multiprocesador va mucho más allá que el multiproceso, ya que los subprocesos dependen de la memoria compartida, esto solo puede haber 8 subprocesos ejecutándose en paralelo en una CPU de 8 núcleos, mientras que el multiprocesamiento puede escalar a miles de procesos paralelos.
fuente