Как ограничить количество потоков, созданных для асинхронного Seq.операция карты в F#?

текущая настройка идет примерно так

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

проблема в том, что это имеет тенденцию создавать слишком много потоков и периодически сбой приложения.

Как ограничить количество потоков в этом случае (скажем, в среде.ProcessorCount)?

3 ответов


если вы хотите распараллелить CPU-интенсивный расчет, который принимает массив (или любую последовательность) в качестве входных данных, то может быть лучше использовать PSeq модуль от Powerpack Языка F# (который доступен только на .NET 4.0, хотя). Он предоставляет параллельные версии многих стандартных Array.xyz функции. Для получения дополнительной информации, вы также можете посмотреть F# для перевода of параллельное программирование с .NET образцы.

код для решения вашей проблема будет немного проще, чем использование рабочих процессов:

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

некоторые различия между двумя вариантами:

  • PSeq создается с помощью Task Parallel Library (TPL) из .NET 4.0, которая оптимизирована для работы с большим количеством CPU-интенсивных задач.
  • асинхронные реализован в библиотеках F# и поддерживает асинхронные (неблокирующие) операции, такие как ввод-вывод при одновременном запуске оперативный.

В общем, если вам нужны асинхронные операции (например, ввод-вывод), то Async - Это лучший вариант. Если у вас есть большое количество CPU-интенсивных задач, то PSeq может быть лучшим выбором (на .NET 4.0)


вот рабочий пример того, как это сделать с помощью семафора, в духе предложения Брайана:

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

есть несколько вещей, которые вы могли бы сделать.

во-первых, так как это использует ThreadPool, вы можете использовать ThreadPool.SetMaxThreads.

во-вторых, вы можете ввести свой собственный дроссель по следующим линиям:

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

makeThrottle() было бы не слишком сложно написать, но это потребовало бы немного накладных расходов на синхронизацию. Если вы пытаетесь распараллелить так много вещей, что у вас заканчивается память, накладные расходы на дроссель, вероятно, не будут проблемой. (Дайте мне знать, если вам нужно пример для такого кода.)

наконец, если это действительно сбой вещи, это пахнет, как вы можете делать что-то неправильно. The ThreadPool обычно (но не всегда) хорошо справляется с управлением. Но в различных обстоятельствах разработка собственного дросселя может быть полезна для вашего приложения в любом случае.