Pregunta

El problema

A pesar de que el código de la que voy a hablar aquí he escrito en C #, que se basa en el marco .NET 4, que no depende específicamente en ninguna particularidad de F # (al menos eso parece!).

Tengo algunas piezas de datos en mi disco que debería actualizar desde la red, ahorrando la versión más reciente en el disco:

type MyData =
    { field1 : int;
      field2 : float }

type MyDataGroup =
    { Data : MyData[];
      Id : int }

// load : int -> MyDataGroup
let load dataId =
    let data = ... // reads from disk
    { Data = data;
      Id = dataId }

// update : MyDataGroup -> MyDataGroup
let update dg =
    let newData = ... // reads from the network and process
                      // newData : MyData[]

    { dg with Data = dg.Data
                     |> Seq.ofArray
                     |> Seq.append newData
                     |> processDataSomehow
                     |> Seq.toArray }

// save : MyDataGroup -> unit
let save dg = ... // writes to the disk

let loadAndSaveAndUpdate = load >> update >> save

El problema es que a loadAndSaveAndUpdate todos mis datos, que tendría que ejecutar la función muchos veces:

{1 .. 5000} |> loadAndSaveAndUpdate

Cada paso haría

  • alguna S de disco,
  • algún crujido de datos,
  • alguna red IO (con posibilidad de un montón de latencia),
  • más datos crujido,
  • y algunos S de disco.

¿No sería bueno tener este hecho en paralelo, hasta cierto punto? Por desgracia, ninguno de mis funciones de lectura y de análisis son "asíncrono flujos de trabajo listo".

Los primeros (no muy bueno) soluciones que se le ocurrió

Tareas

Lo primero que he hecho fue la creación de un Task[] y empezar a todos ellos:

let createTask id = new Task(fun _ -> loadAndUpdateAndSave id)
let tasks = {1 .. 5000}
            |> Seq.map createTask
            |> Seq.toArray

tasks |> Array.iter (fun x -> x.Start())
Task.WaitAll(tasks)

Entonces me golpeó CTRL + ESC sólo para ver cuántos hilos que estaba utilizando. 15, 17, ..., 35, ..., 170, ... hasta que mataron a la aplicación! Algo iba mal.

paralelo

Lo hice casi lo mismo pero utilizando Parallel.ForEach(...) y los resultados fueron los mismos:. Montones y montones y montones de hilos

Una solución que funciona ... tipo de

Entonces decidí comenzar sólo hilos n, Task.WaitAll(of them), a continuación, otra n, hasta que no hubo más tareas disponibles.

Esto funciona, pero el problema es que cuando se ha terminado de procesar, digamos, tareas n-1, se va a esperar, esperar, esperar a que la maldita última tarea que insisten en el bloqueo debido a la gran cantidad de latencia de la red. Esto no es bueno!

Por lo tanto, ¿cómo atacar este problema ? Apreciaría para ver diferentes soluciones, la que se utilizan los flujos de trabajo asincrónicos (y en este caso la forma de adaptar mis funciones no asincrónicos), extensiones paralelas, paralelas patrones extraños, etc.

Gracias.

¿Fue útil?

Solución

¿Está seguro de que sus tareas individuales están terminando en el momento oportuno? Creo que tanto Parallel.ForEach y la clase Task ya usan el .NET subprocesos. Las tareas deben ser generalmente elementos de trabajo de corta duración, en cuyo caso el threadpool sólo generar un pequeño número de hilos reales, pero si sus tareas no están haciendo progresos y hay otras tareas en cola, entonces el número de hilos utilizados, se incrementará hasta el máximo (que por defecto es 250 / procesador en .NET 2.0 SP1, pero es diferente en diferentes versiones del marco). Es también digno de mención que (al menos en .NET 2.0 SP1) de nueva creación del hilo es estrangulado a 2 nuevos temas por segundo, por lo que llegar hasta el número de hilos que estamos viendo indica que las tareas no están terminando en un corto período de tiempo (por lo que puede que no sea completamente exacta de echar la culpa a Parallel.ForEach).

Creo que la sugerencia de Brian utilizar flujos de trabajo async es buena, sobre todo si la fuente de las tareas de larga vida es IO, ya async volverá sus hilos a la subprocesos hasta que el IO completa. Otra opción es aceptar simplemente que sus tareas no están completando rápidamente y permitir que el desove de muchas discusiones (que puede ser controlada hasta cierto punto mediante el uso de System.Threading.ThreadPool.SetMaxThreads) - dependiendo de su situación puede que no sea un gran problema que está utilizando una gran cantidad de hilos.

Otros consejos

ParallelOptions.MaxDegreeOfParallelism límites el número de operaciones simultáneas a cargo de las llamadas método paralelo

El uso de 'asincrónicos le permitirá hacer el I / O-ligado trabajo sin quemar hilos, mientras que las diversas llamadas de E / S son 'en el mar', por lo que sería mi primera sugerencia. Debe ser sencillo para convertir el código para asíncrono, por lo general a lo largo de las líneas de

  • envolver cada cuerpo de la función en async{...}, añadir return cuando sea necesario
  • crear versiones asíncronas de cualquier primitivas de E / S que no están ya en la biblioteca a través de Async.FromBeginEnd
  • llamadas cambia de forma let r = Foo() a let! r = AsyncFoo()
  • Uso Async.Parallel para convertir los objetos asincrónicos 5000 en un solo asíncrono que se ejecuta en paralelo

Hay varios tutoriales para hacer esto; una tal transmisión es rel="noreferrer"> .

Siempre se puede utilizar un ThreadPool.

http://msdn.microsoft.com/en -us / biblioteca / system.threading.threadpool.aspx

básicamente:

  1. Crear un grupo de subprocesos
  2. Establecer el número máximo de hilos
  3. Cola de todas las tareas utilizando QueueUserWorkItem(WaitCallback)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top