如何限制为F#中的异步Seq.map操作创建的线程数?

时间:2010-09-17 22:30:51

标签: multithreading f#

目前的设置是这样的

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

问题是,这会导致创建太多线程并定期崩溃应用程序。

如何在这种情况下限制线程数(比如说,Environment.ProcessorCount)?

3 个答案:

答案 0 :(得分:3)

如果要并行化采用数组(或任何序列)作为输入的CPU密集型计算,那么最好使用F# PowerPack中的PSeq模块(这是仅在.NET 4.0上可用)。它提供了许多标准Array.xyz函数的并行版本。有关详细信息,您还可以查看F# translationParallel Programming with .NET个样本。

解决问题的代码比使用工作流程更简单:

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

这两个选项之间存在一些差异:

  • PSeq 是使用.NET 4.0中的任务并行库(TPL)创建的,该版本针对处理大量CPU密集型任务进行了优化。
  • 异步在F#库中实现,并支持异步(非阻塞)操作,例如并发运行的操作中的I / O.

总之,如果您需要异步操作(例如I / O),那么Async是最佳选择。如果您有大量CPU密集型任务,那么PSeq可能是更好的选择(在.NET 4.0上)

答案 1 :(得分:2)

以下是一个工作示例,说明如何使用信号量,以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

答案 2 :(得分:1)

你可能会做一些事情。

首先,由于这使用了ThreadPool,因此您可以使用ThreadPool.SetMaxThreads

其次,您可以沿着这些方向引入自己的油门:

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

makeThrottle()写起来不会太难,但会产生一点同步开销。如果你试图并行化许多你内存不足的东西,那么节流开销可能不是问题。 (如果您需要这种代码的示例,请告诉我。)

最后,如果这真的是崩溃的东西,它闻起来就像你可能做错了什么。 ThreadPool通常(但并非总是)在管理自身方面做得很好。但在各种情况下,无论如何,设计自己的油门可能对您的应用程序很有价值。