Pregunta

Hay muchos rumores en estos días sobre el uso de bloqueos y el uso de enfoques de paso de mensajes como Erlang. O sobre el uso de estructuras de datos inmutables como en la programación funcional frente a C ++ / Java.

Pero lo que me preocupa es lo siguiente:

  1. AFAIK, Erlang no garantiza la entrega de mensajes. Los mensajes pueden estar perdidos. ¿El algoritmo y el código no se hincharán y volverán a ser complicados si tiene que preocuparse por la pérdida de mensajes? El algoritmo distribuido que utilice no debe depender de la entrega garantizada de mensajes.
  2. ¿Qué pasa si el mensaje es un objeto complicado? ¿No hay una gran penalización en el rendimiento al copiar y enviar los mensajes en lugar de decir mantenerlo en una ubicación compartida (como un DB al que ambos procesos pueden acceder)?
  3. ¿Realmente puedes eliminar totalmente los estados compartidos? No lo creo. Por ejemplo en una base de datos, tiene que acceder y modificar el mismo registro. No puedes usar el mensaje que pasa allí. Debe tener bloqueado o asumir mecanismos de control de concurrencia optimista y luego hacer reversiones en caso de errores. ¿Cómo funciona Mnesia?
  4. Además, no siempre es necesario preocuparse por la concurrencia. Cualquier proyecto también tendrá una gran cantidad de código que no tiene que hacer nada con la concurrencia o las transacciones en absoluto (pero tienen el rendimiento y la velocidad como una preocupación). Muchos de estos algoritmos dependen de estados compartidos (es por eso que el paso por referencia o los punteros son tan útiles).

Teniendo en cuenta este hecho, escribir programas en Erlang, etc., es una molestia porque no puedes hacer nada de esto. Puede ser que haga que los programas sean robustos, pero para cosas como resolver un problema de programación lineal o calcular el casco convexo, etc. el rendimiento es más importante y forzar la inmutabilidad en el algoritmo cuando no tiene nada que ver con la concurrencia / transacciones es una mala decisión . ¿No es así?

¿Fue útil?

Solución

  1. Eso es la vida real : debe tener en cuenta esta posibilidad independientemente del idioma / plataforma. En un mundo distribuido (el mundo real), las cosas fallan: vive con él.

  2. Por supuesto, hay un costo : nada es gratis en nuestro universo. Pero no deberías usar otro medio (por ejemplo, archivo, db) en lugar de transportar " objetos grandes " ¿En tuberías de comunicación? Siempre puedes usar " mensaje " para referirse a " objetos grandes " almacenado en algún lugar.

  3. Por supuesto que no: la idea detrás de la programación funcional / Erlang OTP es " aislar " en la medida de lo posible, las áreas fueron " estado compartido " es manipulado Además, haber marcado claramente lugares donde el estado compartido está mutado ayuda a la capacidad de prueba & amp; trazabilidad.

  4. Creo que te estás perdiendo el punto: no existe una bala de plata. Si su aplicación no se puede construir con éxito usando Erlang, entonces no lo haga. Siempre puede usar otra parte del sistema en general de otra manera, es decir, usar un idioma / plataforma diferente. Erlang no es diferente de otro idioma a este respecto: use la herramienta adecuada para el trabajo correcto .

Recuerde: Erlang fue diseñado para ayudar a resolver problemas concurrentes , asíncronos y distribuidos . No está optimizado para trabajar de manera eficiente en un bloque de memoria compartido, por ejemplo ... a menos que cuente la interfaz con las funciones nif que trabajan en bloques compartidos, parte del juego :-)

Otros consejos

Los sistemas del mundo real siempre son híbridos de todos modos : no creo que los paradigmas modernos intenten, en la práctica, deshacerse de los datos mutables y del estado compartido.

Sin embargo, el objetivo es no necesitar acceso simultáneo a este estado compartido. Los programas se pueden dividir en concurrente y secuencial, y usar el paso de mensajes y los nuevos paradigmas para las partes concurrentes.

No todos los códigos obtendrán la misma inversión : existe la preocupación de que los subprocesos son fundamentalmente " considerados dañinos " ;. Algo como Apache puede necesitar hilos tradicionales concurrentes y una pieza clave de tecnología como esa puede ser cuidadosamente refinada durante un período de años para que pueda explotar con un estado compartido totalmente concurrente. Los núcleos del sistema operativo son otro ejemplo en el que "resuelva el problema sin importar lo costoso que sea" puede tener sentido.

No hay ningún beneficio en el método rápido pero roto : pero para un nuevo código, o un código que no recibe tanta atención, puede darse el caso de que simplemente no sea un hilo seguro, y no manejará la verdadera concurrencia, y por lo tanto la eficiencia relativa " " es irrelevante. Una forma funciona, y una manera no.

No olvide la capacidad de prueba: Además, ¿qué valor puede asignar a las pruebas? La concurrencia de memoria compartida basada en subprocesos simplemente no se puede probar. La concurrencia de paso de mensajes es. Así que ahora tienes la situación en la que puedes probar un paradigma pero no el otro. Entonces, ¿cuál es el valor de saber que el código ha sido probado? ¿El peligro de no saber si el otro código funcionará en todas las situaciones?

Hay algunas suposiciones implícitas en sus preguntas: usted asume que todos los datos pueden caber en una máquina y que la aplicación está localizada intrínsecamente en un solo lugar.

¿Qué sucede si la aplicación es tan grande que no cabe en una máquina? ¿Qué sucede si la aplicación supera una máquina?

No desea tener una forma de programar una aplicación si cabe en una máquina y una forma completamente diferente de programarlo tan pronto como supere una máquina.

¿Qué pasa si quieres hacer una aplicación tolerante a fallos? Para hacer que algo sea tolerante a fallas, necesita al menos dos máquinas separadas físicamente y no compartirlas . Cuando hablas de compartir y bases de datos, omites mencionar que cosas como mySQL clúster lograr tolerancia a fallos precisamente mediante el mantenimiento de copias sincronizadas de la datos en máquinas separadas físicamente - hay una gran cantidad de paso de mensajes y Copiando lo que no ves en la superficie: Erlang simplemente expone esto.

La forma en que programa no debe cambiar repentinamente para adaptarse a la tolerancia a fallos y la escalabilidad.

Erlang fue diseñado principalmente para crear aplicaciones tolerantes a fallas.

Los datos compartidos en un núcleo múltiple tienen su propio conjunto de problemas: cuando accede a datos compartidos necesita adquirir un bloqueo: si utiliza un bloqueo global (el método más sencillo) puede terminar detener todos los núcleos mientras accede a los datos compartidos. Acceso a datos compartidos en un multinúcleo puede ser problemático debido a problemas de almacenamiento en caché, si los núcleos tienen cachés de datos locales y luego acceden a " lejos " Los datos (en la memoria caché de otros procesadores) pueden ser muy costosos.

Muchos problemas están distribuidos intrínsecamente y los datos nunca están disponibles en un solo lugar al mismo tiempo, este tipo de problemas encaja bien con la forma de pensar de Erlang.

En una configuración distribuida " garantizando la entrega de mensajes " es imposible : es posible que la máquina de destino se haya bloqueado. Erlang no puede garantizar la entrega de mensajes. adopta un enfoque diferente: el sistema le dirá si no pudo enviar un mensaje (pero solo si ha usado el mecanismo de enlace) - entonces puede escribir su propio error personalizado recuperación.)

Para el cálculo de números puros, Erlang no es apropiado, pero en un sistema híbrido Erlang es bueno para gestionar cómo se distribuyen los cálculos a los procesadores disponibles, por lo que vemos muchos sistemas en los que Erlang administra la distribución y los aspectos tolerantes a fallas del problema, pero el problema en sí se resuelve en un idioma diferente.

y otros idiomas se utilizan

Algunos comentarios sobre el malentendido que tienes de Erlang:

  • Erlang garantiza que los mensajes no se perderán y que llegarán en el orden enviado. Una situación de error básica es que la máquina A no puede hablar con la máquina B. Cuando eso sucede, el proceso se monitorea y se activarán los enlaces y se enviarán mensajes de desconexión del sistema a los procesos que se registraron. Nada se dejará caer silenciosamente. Los procesos se " se estrellarán " y los supervisores (si los hay) intentan reiniciarlos.
  • Los objetos no se pueden mutar, por lo que siempre se copian. Una forma de asegurar la inmutabilidad es mediante la copia de valores a los montones de otros procesos de Erlang. Otra forma es asignar objetos en un montón compartido, las referencias del mensaje a ellos y simplemente no tener ninguna operación que los mute. ¡Erlang hace el primero en rendimiento! El tiempo real sufre si necesita detener todos los procesos para recolectar basura en un montón compartido. Pregunta a Java.
  • Hay un estado compartido en Erlang. Erlang no está orgulloso de eso, pero es pragmático al respecto. Un ejemplo es el registro de procesos local, que es un mapa global que asigna un nombre a un proceso para que los procesos del sistema puedan reiniciarse y reclamar su nombre anterior. Erlang simplemente intenta evitar el estado compartido si es posible . Las tablas ETS que son públicas son otro ejemplo.
  • Sí, a veces Erlang es demasiado lento. Esto sucede en todos los idiomas. A veces Java es demasiado lento. A veces C ++ es demasiado lento. Solo porque un circuito cerrado en un juego tuvo que bajar al ensamblaje para comenzar con algunas matemáticas vectoriales basadas en SIMD, no se puede deducir que todo se debe escribir en ensamblaje porque es el único lenguaje que es rápido cuando importa. Lo que importa es poder escribir sistemas que tengan un buen rendimiento, y Erlang se las arregla bastante bien. Ver puntos de referencia en las guiñadas o rabbitmq.

Sus hechos no son hechos sobre Erlang. Incluso si crees que la programación de Erlang es una molestia, encontrarás que otras personas crean un software increíble gracias a él. Debes intentar escribir un servidor IRC en Erlang, o algo muy concurrente. Incluso si nunca va a utilizar Erlang nuevamente, habría aprendido a pensar en la concurrencia de otra manera. Pero, por supuesto, lo harás, porque Erlang es muy fácil.

Los que no entienden a Erlang están condenados a volver a implementarlo mal.

Bueno, el original era sobre Lisp, pero ... ¡es verdad!

  

Por ejemplo, en una base de datos, debe acceder y modificar el mismo registro

Pero eso es manejado por el DB. Como usuario de la base de datos, simplemente ejecuta la consulta y la base de datos garantiza que se ejecute de forma aislada.

En cuanto al rendimiento, una de las cosas más importantes para eliminar el estado compartido es que permite nuevas optimizaciones. El estado compartido no es particularmente eficiente. Obtiene combates de núcleos sobre las mismas líneas de caché, y los datos deben escribirse en la memoria, de lo contrario, podrían permanecer en un registro o en el caché de la CPU.

Muchas optimizaciones del compilador también dependen de la ausencia de efectos secundarios y del estado compartido.

Podría decir que un lenguaje más estricto que garantiza estas cosas requiere más optimizaciones para ser más eficaz que algo como C, pero también hace que estas optimizaciones sean mucho más fáciles de implementar para el compilador.

Muchas preocupaciones similares a las cuestiones de concurrencia surgen en el código de cadena única. Las CPU modernas están canalizadas, ejecutan las instrucciones fuera de orden y pueden ejecutar 3-4 de ellas por ciclo. Entonces, incluso en un programa de un solo hilo, es vital que el compilador y la CPU puedan determinar qué instrucciones pueden intercalarse y ejecutarse en paralelo.

  1. Erlang proporciona a los supervisores y devoluciones de llamada gen_server para llamadas sincrónicas, por lo que sabrá si no se entrega un mensaje: o la llamada gen_server devuelve un tiempo de espera, o todo el nodo se activará y desactivará si el supervisor se activa .
  2. por lo general, si los procesos se encuentran en el mismo nodo, los idiomas de paso de mensajes optimizan la copia de los datos, por lo que es casi como una memoria compartida, excepto si el objeto se cambia por los dos, lo que no se puede hacer usando la memoria compartida. de todos modos
  3. Hay algún estado que los procesos mantienen al pasarlo a sí mismos en las llamadas de cola recursivas, y, por supuesto, también se puede pasar algún estado a través de los mensajes. No uso mucho mnesia, pero es una base de datos transaccional, por lo que una vez que haya pasado la operación a mnesia (y haya regresado), está casi seguro de que se realizará ...
  4. Es por eso que es fácil vincular estas aplicaciones a erlang con el uso de puertos o controladores. Los más fáciles son los puertos, es como un conducto de Unix, aunque creo que el rendimiento no es tan bueno ... y como se dijo, el paso de mensajes generalmente termina siendo un puntero que pasa de todos modos, ya que la VM / compilador optimiza la copia de la memoria .

Para la corrección, compartir es el camino a seguir, y mantener los datos lo más normalizados posible. Para la inmediatez, envíe mensajes para informar sobre los cambios, pero siempre haga una copia de seguridad de ellos con sondeo. Los mensajes se caen, se duplican, se vuelven a ordenar, se retrasan, no confíe en ellos.

Si lo que te preocupa es la velocidad, primero hazlo de un solo hilo y sintonice las luces del día . Luego, si tiene varios núcleos y sabe cómo dividir el trabajo, use el paralelismo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top