Как ограничить количество потоков, созданных для асинхронного 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
обычно (но не всегда) хорошо справляется с управлением. Но в различных обстоятельствах разработка собственного дросселя может быть полезна для вашего приложения в любом случае.