2009-10-18 20 views
6

leí un article que afirma que los cierres (o "bloques") son un arma útil en la "guerra contra el multicores", porque¿Por qué los cierres repentinamente son útiles para optimizar programas que se ejecutan en múltiples núcleos?

[...] que le permiten crear unidades de trabajo, que cada uno tiene su propia copia de la pila, y no pise en cada otros dedos como resultado. Además, puede pasar estas unidades como que son valores, cuando en realidad contienen una pila completa de valores (juego de palabras), y el código ejecutable a realiza alguna operación.

Ahora, estoy no debatir la utilidad de los cierres en general y posiblemente también para la programación concurrente en un modelo de memoria compartida, pero ¿cuál es la diferencia con un hilo que sólo actúa en los datos locales (o procesos, o actores, o ...)?

¿No es un cierre en sí mismo útil para la programación simultánea como un hilo sin un programador?

¿Qué hay con los cierres que tienen efectos secundarios no locales?

+0

Parece que el artículo está cambiando la definición de cierres para que signifique algo más allá de lo que estoy acostumbrado a que signifique. –

Respuesta

4

El argumento es que tener cierres en el lenguaje de programación facilita el trabajo en otro hilo. Creo que el autor debería haber mencionado la importancia de la función de orden superior en ese argumento.

Mi introducción favorita a las funciones de orden superior es "Why functional programming matters", no intentaré presentar una mala réplica aquí.

Así que usar cierres no le da paralelismo gratis si va a ejecutar cierres en bucles for, por ej.

for (int i = 0; i < numElements; i++) { 
    result[i] = closure(inputs[i], i); 
} 

porque la lengua no puede decir si closure(a, b) alguna manera cambia otros valores en el resultado o entradas matrices. Pero los lenguajes con funciones de orden superior como map especifican que la función pasada a map no debe mirar o cambiar otros valores en las entradas, y evitar que afecte a otros resultados. Por lo tanto, el código como el siguiente, que es común en los lenguajes funcionales, pueden ser paralelizados para usted, sin necesidad de que crear un grupo de subprocesos de trabajo y la mano del cierre a ellos:

results = map(closure, inputs, [0..numElements-1]); 

En estos idiomas, cierres quita el dolor de declarar una nueva función en algún lugar para piezas cortas de código. Eso hace que sea más divertido usar funciones de orden superior.

El siguiente código Haskell define una función f que toma una lista de números y devuelve una lista donde cada entrada i se reemplaza por 2i+1. Por le ahorra la molestia de crear una función para calcular 2i+1 esto es 1 línea de código en lugar de 2.

f nums = map (\i -> 2*i+1) nums 

Una vez más, ver "Why functional programming matters" de argumentos fuertes en cuanto a cómo esta escala hasta bases de código reales.

+2

Pero aún así la conclusión es que los cierres son útiles porque son cierres (cortos y dulces) pero no porque intrínsecamente ofrecen algunas ventajas al hacer una programación simultánea. No hay conexión entre cierres y concurrencia. – eljenso

2

Aquí es un buen definición de cierres:

A "cierre" es una expresión (típicamente una función) que puede tener variables libres junto con un entorno que se une esas variables (que " cierra "la expresión".

Creo que son confusas las definiciones, ya que, en javascript, por ejemplo, mis cierres a menudo pueden tener efectos secundarios no locales, ya que estoy cambiando el DOM.

Los cierres son muy útiles, por lo que C# los agregó al idioma.

En idiomas como el lenguaje de programación funcional, parece que no necesariamente crean subprocesos, que tiene que pagar un precio debido al cambio de contexto, sino que crean procesos ligeros. El marco o compilador tendrá control sobre qué crear para garantizar que el procesador se utilice mejor.

Si escribe con cierres es menos importante que si usa datos inmutables.

Por ejemplo, si tengo una aplicación que no tiene datos globales, pero cada hilo usa su propia copia local, corresponde al sistema operativo y al programador determinar qué núcleos usará mi aplicación. Desafortunadamente, en C/C++ los compiladores no saben cómo hacerlo bien, así que al pasar a FP podemos ir con frameworks, como Erlang, que han estado tratando con el procesamiento distribuido durante mucho tiempo, y apalancamiento su experiencia.

Los actores, en algo así como Erlang, tendrán menos sobrecarga que un hilo C/C++, ya que el cambio parece ser más rápido con los actores.

+0

Los efectos secundarios no locales son solo un problema que tengo con los cierres como 'la' estructura útil para la concurrencia. Por lo tanto, no es un problema de mi definición de cierre (que es el mismo que el suyo). Solo dudo seriamente de que los cierres por sí mismos sean útiles para la programación concurrente sin restricciones y mecanismos adicionales establecidos, por lo que no son más (o menos) adecuados que otros constructos para la concurrencia. – eljenso

+0

Hay problemas que deben resolverse cuando se utilizan cierres, ya que es posible que cambie el valor del que depende, por lo que debe lidiar con eso. –

+0

Creo que mencioné que se necesitan más cierres para ser útiles, pero los cierres pueden ser útiles para ayudar a simplificar y generalizar el código, que es lo que mostraba el artículo que mencionó en su ejemplo. Pero, como con cualquier cosa, necesitas saber qué está pasando, para que no te lastimes por las complicaciones de la herramienta. –

2

Esta cita del artículo de paquetes de un buen número de malentendidos en fragmento de una frase:

[...] que permitirá crear unidades de trabajo, que cada uno tiene su propia copia de la pila , y no pise el cada dedos del pie como resultado.

Esto generalmente no es cierto para los idiomas con cierres.

En primer lugar, más a menudo tienen referencias a la pila, no copias, en aras de la eficiencia. Y en la mayoría de los idiomas, puede modificar cosas a través de una referencia. Entonces, en esos idiomas, esta característica no proporciona una forma de aislar unidades de trabajo. En todo caso, puede hacerlo más confuso.

En segundo lugar, en la mayoría de los idiomas (cuerdos) puede consultar todo lo que encierra léxicamente una función local. No puedes referirte a cualquier cosa en cualquier lugar de la pila. Por ejemplo, no puede profundizar en las variables locales de la función que llamó a la función que llamó a la función ... etc. Solo puede acceder a las variables/parámetros de funciones declaradas localmente, cuyo texto incluye el texto en el que se produce el uso. Además, las variables locales y los parámetros ("en la pila") no son las únicas cosas que pueden estar encubriendo léxicamente una función. Entonces, "la pila" es el concepto incorrecto para invocar aquí.

Java es un ejemplo de un lenguaje que solo puede tomar copias de variables locales y parámetros en sus cierres de "clase interna anónima". Sin embargo, aún tomará una referencia en el caso de la referencia this de la clase externa.

Y en el caso de cerrar sobre this, la clase interna ahora almacenará una referencia implícita a la clase externa - nada que ver con la pila, realmente.

La situación es similar en C#, excepto que las variables locales y los parámetros se capturan por referencia en lugar de copiarse.

var counter = 0; 
Repeat(10,() => counter++); 

Supongamos Repeat es una función de biblioteca que comienza otro hilo y ahora va a llamar a la Action lambda le pasa cada 10 milisegundos. ¡Con suerte puede ver cómo esta es una forma muy concisa de crear condiciones de carrera!

El único tipo de lenguaje que evitaría este problema sería un lenguaje funcional puro tal como Haskell, pero eso no sería debido a los cierres - claramente - pero en su lugar debido al hecho de que nunca se puede modificar cualquier compartida estado. (Y Haskell todavía no evitaría el problema por completo, la mayoría del software real tiene que interactuar con un estado compartido externo al programa en algún momento, y la biblioteca estándar de Haskell tiene una forma de hacerlo).

Cuestiones relacionadas