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。