将代理与F#中的任务组合

时间:2013-01-07 15:49:40

标签: concurrency f#

我在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而不使用区分联合作为代理的消息类型(以支持询问消息)?

2 个答案:

答案 0 :(得分:2)

如果没有一个完整的例子,我发现很难理解你的代码做了什么(也许是因为它结合了很多不同的并发编程原语,这使得它有点难以理解)。

无论如何,MailboxProcessor的主体只执行一次(如果你想使用普通代理获得并发,你需要启动多个代理)。在代理正文中,您为每个problemDef.generators启动运行chunk的任务。

这意味着problemDef.generators应该并行运行。但是,调用foundSoFar.ContainsfoundSoFar.UnionWith以及Seq.distinct的代码始终按顺序运行。

因此,如果problemDef.generators是一个简单而有效的函数,跟踪foundSoFar(顺序完成)的开销可能比并行化所得的开销大。

我不熟悉MSet<'T>,但如果是(或者你用它替换)一个线程安全的可变集,那么你应该能够在{{1}中运行一些并集权限(与其他联合并行)。

PS:正如我所说,如果不运行代码就很难分辨,所以我的想法可能完全错了!

答案 1 :(得分:1)

您将高级并发原语(任务和代理)与ManualResetEventSlim混合在一起非常糟糕。您可以使用PostAndReply吗?

您正在使用Seq在生成的任务中执行“工作”这是懒惰的,因此在发回之后它实际上不会执行任何操作。您是否可以使用Array.ofSeq

之类的内容强制评估任务

您使用Task的方式是异常的。切换到Async.Start可能更惯用。

如果没有完整的解决方案,我无法验证我的任何猜测......

  认为充分同时使用4核

你的多核并行性的心理模型可能非常不合适。