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

Multithreading 如何限制在F#中为异步Seq.map操作创建的线程数?,multithreading,f#,Multithreading,F#,当前的设置是这样的 array |> Seq.map (fun item -> async { return f item}) |> Async.Parallel |> Async.RunSynchronously 问题是,这往往会创建太多线程,并周期性地使应用程序崩溃 在这种情况下,如何限制线程的数量(例如,限制为Environment.ProcessorCount) 你可以做几件事 首先,由于这使用了ThreadPool,因此可以使用ThreadPool.SetMa

当前的设置是这样的

array
|> Seq.map (fun item -> async { return f item})
|> Async.Parallel
|> Async.RunSynchronously
问题是,这往往会创建太多线程,并周期性地使应用程序崩溃


在这种情况下,如何限制线程的数量(例如,限制为Environment.ProcessorCount)

你可以做几件事

首先,由于这使用了
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
通常(但并非总是)能够很好地管理自身。但是,在各种情况下,设计自己的节流阀可能对应用程序很有价值。

如果您想并行化以数组(或任何序列)为输入的CPU密集型计算,那么最好使用中的
PSeq
模块(该模块仅在.NET 4.0上可用)。它提供了许多标准
Array.xyz
函数的并行版本。有关更多信息,您还可以查看示例的列表

解决问题的代码比使用工作流要简单一些:

array |> PSeq.map f
      |> PSeq.toArray 
这两个选项之间的一些区别是:

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

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

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

我对此感到困惑。我的印象是F#已经使用了某种具有处理器数量限制的线程池。不是吗?正如Zan上面提到的,我相信Async可以使用具有上限的线程池。你确定问题不在
f
范围内吗?那么问题就变成了,你能手动设置该线程池成员资格的上限吗?据我所知,最大数目由
System.Threading.ThreadPool.SetMaxThreads
指定。不过,我没有检验这个假设。(在.NET中每个进程只有一个线程池。)看到崩溃的堆栈跟踪了吗?我们用不同的方法解决了它,但这是一个很好的答案。不幸的是,我们不能使用.NET4.0。