我在F#中有以下代码,认为可以充分同时使用我的机器的4个核心。然而,CPU使用仅限于一个核心。
member x.Solve problemDef =
use flag = new ManualResetEventSlim(false)
let foundSoFar = MSet<'T>()
let workPile = MailboxProcessor<seq<'T>>.Start(fun inbox ->
let remaining = ref 0
let rec loop() = async {
let! data = inbox.Receive()
let data = data |> Seq.filter (not << foundSoFar.Contains) |> Array.ofSeq
foundSoFar.UnionWith data
let jobs = ref -1
for chunk in data |> Seq.distinct |> Seq.chunked 5000 do
Async.Start <| async {
Seq.collect problemDef.generators chunk
|> Array.ofSeq
|> inbox.Post
}
incr jobs
remaining := !remaining + !jobs
if (!remaining = 0 && !jobs = -1) then
flag.Set() |> ignore
else
return! loop()
}
loop()
)
workPile.Post problemDef.initData
flag.Wait() |> ignore
foundSoFar :> seq<_>
我使用MailboxProcessor作为工作堆,从中我获取元素块,通过HashSet过滤它们并使用新元素创建任务,其结果插入到工作堆中。重复此过程直到不生成新元素。此代码的目的是异步插入工作堆中的块,从而使用任务。我的问题是没有并行性。
编辑:感谢@ jon-harrop我解决了并发问题,这是由于seq的懒惰性质,并根据建议重新编写代码。有没有办法摆脱ManualResetEvent而不使用区分联合作为代理的消息类型(以支持询问消息)?
答案 0 :(得分:2)
如果没有一个完整的例子,我发现很难理解你的代码做了什么(也许是因为它结合了很多不同的并发编程原语,这使得它有点难以理解)。
无论如何,MailboxProcessor
的主体只执行一次(如果你想使用普通代理获得并发,你需要启动多个代理)。在代理正文中,您为每个problemDef.generators
启动运行chunk
的任务。
这意味着problemDef.generators
应该并行运行。但是,调用foundSoFar.Contains
和foundSoFar.UnionWith
以及Seq.distinct
的代码始终按顺序运行。
因此,如果problemDef.generators
是一个简单而有效的函数,跟踪foundSoFar
(顺序完成)的开销可能比并行化所得的开销大。
我不熟悉MSet<'T>
,但如果是(或者你用它替换)一个线程安全的可变集,那么你应该能够在{{1}中运行一些并集权限(与其他联合并行)。
答案 1 :(得分:1)
您将高级并发原语(任务和代理)与ManualResetEventSlim
混合在一起非常糟糕。您可以使用PostAndReply
吗?
您正在使用Seq
在生成的任务中执行“工作”这是懒惰的,因此在发回之后它实际上不会执行任何操作。您是否可以使用Array.ofSeq
?
您使用Task
的方式是异常的。切换到Async.Start
可能更惯用。
如果没有完整的解决方案,我无法验证我的任何猜测......
认为充分同时使用4核
你的多核并行性的心理模型可能非常不合适。