[英]Combining Agents with Tasks in 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,而無需使用區分的聯合作為代理的消息類型(以支持詢問消息)?
沒有完整的示例,我發現很難理解您的代碼的功能(也許是因為它結合了許多不同的並發編程原語,這使其難以理解)。
無論如何, MailboxProcessor
的主體僅執行一次(如果要使用純代理獲取並發,則需要啟動多個代理)。 在代理程序主體中,您啟動一個任務,該任務為每個chunk
運行problemDef.generators
。
這意味着problemDef.generators
應該並行運行。 但是,調用foundSoFar.Contains
和foundSoFar.UnionWith
以及Seq.distinct
的代碼始終按順序運行。
因此,如果problemDef.generators
是一個簡單高效的函數,則跟蹤foundSoFar
的開銷(按順序進行)可能比並行化要大。
我不熟悉MSet<'T>
,但是如果它是(或如果替換為)線程安全可變集,則您應該能夠在Task.StartNew
中並行運行一些Task.StartNew
(並行與其他聯合)。
PS:正如我所說,不運行代碼就很難說,所以我的想法可能是完全錯誤的!
您正在將高級並發原語(任務和代理)與ManualResetEventSlim
,這很糟糕。 您可以改用PostAndReply
嗎?
您正在使用Seq
在產生的任務中執行“工作”,這是很懶的,因此直到將其回發后它實際上不會做任何事情。 您可以使用Array.ofSeq
類的Array.ofSeq
強制執行任務內部的評估嗎?
您使用Task
是異常的。 切換到Async.Start
可能更慣用。
沒有完整的解決方案,我將無法驗證我的任何猜測...
認為足夠並發以利用4個核心
您關於多核並行性的思維模型可能還差得很遠。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.