2010-09-17 8 views
7

La configuración actual es algo como esto¿Cómo limitar el número de subprocesos creados para una operación asíncrona de Seq.map en F #?

array 
|> Seq.map (fun item -> async { return f item}) 
|> Async.Parallel 
|> Async.RunSynchronously 

El problema es que esto tiende a crear demasiados hilos y bloquear la aplicación periódicamente.

Cómo limitar el número de subprocesos en este caso (a, por ejemplo, Environment.ProcessorCount)?

+0

Estoy confundido por esto. Tenía la impresión de que F # utilizaba algún tipo de grupo de subprocesos con una limitación de recuento de procesador. ¿No es verdad? –

+1

Como Zan mencionó anteriormente, creo que Async funciona con un grupo de subprocesos que tiene un límite superior. ¿Estás seguro de que el problema no está dentro de 'f'? – Paul

+1

Luego surge la pregunta: ¿se puede establecer el límite superior en la membresía de ese grupo de subprocesos manualmente? – Alexander

Respuesta

3

Si desea paralelizar el cálculo intensivo de la CPU que toma una matriz (o cualquier secuencia) como entrada, entonces puede ser una mejor idea usar el módulo PSeq del F# PowerPack (que está disponible solo en .NET 4.0, aunque) Proporciona versiones paralelas de muchas funciones estándar Array.xyz. Para obtener más información, también puede mirar F# translation de Parallel Programming with .NET muestras.

El código para resolver su problema sería un poco más simple que utilizan los flujos de trabajo:

array |> PSeq.map f 
     |> PSeq.toArray 

Algunas diferencias entre las dos opciones son:

  • PSEQ se ha creado usando tarea paralela Biblioteca (TPL) de .NET 4.0, que está optimizado para trabajar con una gran cantidad de tareas intensivas de CPU.
  • Async se implementa en las bibliotecas F # y admite operaciones asincrónicas (sin bloqueo) como E/S en las operaciones que se ejecutan simultáneamente.

En resumen, si necesita operaciones asincrónicas (por ejemplo, E/S), entonces Async es la mejor opción. Si tiene una gran cantidad de tareas intensivas en CPU, entonces PSeq puede ser una mejor opción (en .NET 4.0)

+0

Lo hemos resuelto de manera diferente, pero esta es una buena respuesta. Desafortunadamente, no podemos usar .NET 4.0. – Alexander

1

Hay un par de cosas que puede hacer.

En primer lugar, ya que este usa el ThreadPool, puede usar ThreadPool.SetMaxThreads.

En segundo lugar, se puede introducir su propia aceleración a lo largo de estas líneas:

let throttle = makeThrottle(8) 
array 
|> Seq.map (fun item -> async { do! throttle.Wait() 
           return f item}) 
|> Async.Parallel 
|> Async.RunSynchronously 

makeThrottle() no sería demasiado difícil escribir, pero sería incurrir en una pequeña sobrecarga de sincronización. Si intentas paralelizar tantas cosas que te estás quedando sin memoria, es probable que la aceleración del acelerador no sea un problema. (Avíseme si necesita una muestra para este tipo de código.)

Finalmente, si esto realmente está estropeando las cosas, huele como si estuviera haciendo algo mal. El ThreadPool normalmente (pero no siempre) hace un buen trabajo gestionándose solo. Pero en diversas circunstancias, diseñar su propio acelerador puede ser valioso para su aplicación de todos modos.

2

Aquí hay un ejemplo práctico de cómo hacer esto usando un semáforo, en el espíritu de la sugerencia de Brian:

open System 

let throttle n fs = 
    seq { let n = new Threading.Semaphore(n, n) 
      for f in fs -> 
       async { let! ok = Async.AwaitWaitHandle(n) 
         let! result = Async.Catch f 
         n.Release() |> ignore 
         return match result with 
          | Choice1Of2 rslt -> rslt 
          | Choice2Of2 exn -> raise exn 
        } 
     } 

let f i = async { printfn "start %d" i 
        do! Async.Sleep(2000) 
       } 
let fs = Seq.init 10 f 

fs |> throttle 2 |> Async.Parallel |> Async.RunSynchronously |> ignore 
Cuestiones relacionadas