2010-05-05 28 views
12

He estado repasando la funcionalidad de algunas de las nuevas características paralelas en .Net 4.0.Funciones paralelas en .Net 4.0

Decir que tengo código de este modo:

foreach (var item in myEnumerable) 
    myDatabase.Insert(item.ConvertToDatabase()); 

Imagínese myDatabase.Insert está realizando un trabajo para insertar a una base de datos SQL.

En teoría se podría escribir:

Parallel.ForEach(myEnumerable, item => myDatabase.Insert(item.ConvertToDatabase())); 

y automáticamente obtener el código que se aprovecha de múltiples núcleos.

Pero, ¿qué ocurre si myEnumerable solo puede interactuar con un solo hilo? ¿La clase Parallel enumerará por un solo hilo y solo distribuirá el resultado a los hilos de trabajo en el ciclo?

¿Qué ocurre si myDatabase solo puede interactuar con un único hilo? Ciertamente, no sería mejor hacer una conexión de base de datos por iteración del ciclo.

Finalmente, ¿qué ocurre si mi "var item" es un UserControl o algo con lo que se debe interactuar en el hilo de UI?

¿Qué patrón de diseño debo seguir para resolver estos problemas?

Me parece que el cambio a Parallel/PLinq/etc no es exactamente fácil cuando se trata de aplicaciones del mundo real.

Respuesta

12

La interfaz IEnumerable<T> no es intrínsecamente segura para subprocesos. Parallel.ForEach se encargará automáticamente de esto, y solo paralelizará los elementos que salen de su enumeración. (La secuencia siempre se recorrerá, un elemento a la vez, en orden, pero los objetos resultantes se paralelizarán).

Si sus clases (es decir, la T) no pueden ser manejadas por múltiples hilos, entonces no debería intentar para paralelizar esta rutina. No todas las secuencias son candidatas para la paralelización, que es una de las razones por las cuales el compilador no realiza esto automáticamente)

Si está trabajando y necesita trabajar con el hilo de la interfaz de usuario, esto aún es posible. Sin embargo, deberá tener la misma atención que cuando quiera que trate elementos de la interfaz de usuario en hilos de fondo, y ordenar los datos nuevamente en el hilo de la interfaz de usuario. Esto se puede simplificar en muchos casos usando la nueva API TaskScheduler.FromCurrentSynchronizationContext. Escribí sobre this scenario on my blog here.

+1

Mejor respuesta hasta el momento, pregunta lateral: decir que mi cuerpo de bucle realiza una operación IO de larga ejecución (solicitud de red, base de datos, etc.), ¿detectará la clase Parallel hilos suspendidos/suspendidos y automáticamente iniciará uno nuevo? ¿O estará limitado a la cantidad de núcleos en la máquina? – jonathanpeppers

+0

@ Jonathan.Peppers: el programador de tareas predeterminado maneja esto bastante bien. Inyectará trabajo adicional en la situación. (De forma predeterminada, ThreadPool usa muchos más elementos que subprocesos, y reduce la escala según la carga de trabajo de forma dinámica) –

2

A medida que han conjeturado, aprovechando Parallel.For o Parallel.ForEach requiere que tenga la capacidad de componer su trabajo en unidades discretas (encarnado por su estado de lambda que se pasa a la Parallel.ForEach) que se puede ejecutar de forma independiente .

+0

¿Hay algún problema en el mundo real que cumpla con este criterio? En otras palabras, ¿la aplicación promedio incluso podrá usar estas características paralelas? – jonathanpeppers

+0

@Jonathan: Absolutamente. Eche un vistazo a esta presentación de Scott Hanselman, donde muestra un vívido ejemplo de cómo funciona esto. http://channel9.msdn.com/posts/matthijs/Lap-Around-NET-4-with-Scott-Hanselman/ La demostración comienza a los 38 minutos, a los 55 segundos de la charla y finaliza a las 47:02. –

+0

Aparentemente su sitio web tiene problemas para saltar a 38:55, tendré que verlo todo en mi casa y responderle. Todavía soy escéptico de que den un buen ejemplo. – jonathanpeppers

0

hay una gran discusión en las respuestas y comentarios aquí: Parallel.For(): Update variable outside of loop.

La respuesta es no: las extensiones paralelas no serán adecuadas para usted. Los problemas de subprocesos múltiples siguen siendo reales aquí. Esta es una buena sintaxis de azúcar, pero no es una panacea.

+0

Es un poco más que azúcar sintáctica.Por ejemplo, puede especificar el grado de paralelismo y conectar una rutina de cancelación que desenrollará con gracia todos los hilos. –

6

Todos estos son problemas legítimos, y PLINQ/TPL no intentan solucionarlos. Sigue siendo su trabajo como desarrollador escribir código que pueda funcionar correctamente cuando se paraleliza. No hay magia que el compilador/TPL/PLINQ pueda hacer para convertir código que no es seguro para el subprocesamiento múltiple en código seguro para subprocesos ... debe asegurarse de hacerlo.

Para algunas de las situaciones que describió, primero debe decidir si la paralelización es incluso sensata. Si el cuello de botella adquirirá conexión a una base de datos o asegurará la secuencia correcta de las operaciones, entonces tal vez el subprocesamiento múltiple no sea apropiado.

En el caso de cómo TPL transmite un enumerable a múltiples hilos, su suposición es correcta. La secuencia se enumera en un único subproceso y cada elemento de trabajo se distribuye (potencialmente) a un subproceso independiente para que actúe. La interfaz IEnumerable<T> es inherentemente no threadsafe, pero TPL maneja esto detrás de escena para usted.

Lo que PLINQ/TPL puede ayudarlo a hacer, es administrar cuándo y cómo despachar trabajos a múltiples hilos. El TPL detecta cuando hay múltiples núcleos en una máquina y escala automáticamente el número de subprocesos utilizados para procesar los datos. Si una máquina solo tiene una sola CPU/Core, entonces TPL puede elegir para no paralelizar el trabajo. El beneficio para usted, el desarrollador, no es tener que escribir dos rutas diferentes: una para lógica paralela, otra para secuencia. Sin embargo, la responsabilidad sigue siendo suya para asegurarse de que se pueda acceder de forma segura a su código desde varios hilos de manera simultánea.

¿Qué patrón de diseño debo seguir a para resolver estos problemas?

No hay una respuesta a esta pregunta ... sin embargo, una práctica general es emplear immutability en el diseño de su objeto. La inmutabilidad hace que sea más seguro consumir un objeto a través de múltiples hilos y es una de las prácticas más comunes para hacer que las operaciones sean paralelas. De hecho, los lenguajes como F # hacen uso de la inmutabilidad extensivamente para permitir que el lenguaje ayude a facilitar la programación simultánea.

Si está en .NET 4.0, también debe consultar las clases de colecciones ConcurrentXXX en System.Collections.Concurrent. Aquí es donde encontrará algunas construcciones de bloqueo de bloqueo sin fisuras y de grano fino que simplifican la escritura del código multiproceso.