[英]A MailboxProcessor that operates with a LIFO logic
我正在學習F#代理( MailboxProcessor
)。
我正在處理一個非常常規的問題。
dataSource
),它是流數據的源。 數據必須由代理數組( dataProcessor
)處理。 我們可以將dataProcessor
視為某種跟蹤設備。 dataProcessor
可能處理其輸入的速度快。 我正在探索解決此問題的方法。
第一個想法是在dataSource
實現堆棧 (LIFO)。 當dataProcessor
可用於接收和處理數據時, dataSource
將發送可用的最新觀察值。 該解決方案可能有效,但由於可能需要阻止並重新激活dataProcessor
因此可能會變dataProcessor
復雜。 並將其狀態傳達給dataSource
,從而導致雙向通訊問題。 這個問題可能歸結為消費者-生產者問題中的blocking queue
,但是我不確定。
第二個想法是讓dataProcessor
負責消息排序。 在這種體系結構中, dataSource
只會將更新發布到dataProcessor
的隊列中。 dataProcessor
將使用“ Scan
來獲取隊列中可用的最新數據。 這可能是要走的路。 但是,我不確定在當前的MailboxProcessor
設計中是否可以清除消息隊列,刪除較舊的過時消息。 此外, 在這里 ,寫道:
不幸的是,當前版本的F#中的TryScan函數以兩種方式被破壞。 首先,重點是指定一個超時,但是實現實際上並沒有兌現它。 具體來說,無關的消息會重置計時器。 其次,與其他“掃描”功能一樣,將在鎖定下檢查消息隊列,該鎖定可防止在掃描期間(可能是任意長時間)發布任何其他線程。 因此,TryScan函數本身傾向於鎖定並發系統,甚至可能引入死鎖,因為調用者的代碼是在鎖內求值的(例如,當鎖下的代碼阻塞等待時,從函數參數到Scan或TryScan的發布會死鎖代理。獲取它已經在下面的鎖)。
使最新的觀測結果反彈可能是一個問題。 這篇文章的作者@Jon Harrop建議
我設法圍繞它進行了架構,並且最終的架構實際上更好。 本質上,我渴望使用我自己的本地隊列
Receive
所有消息並進行過濾。
這個想法肯定值得探索,但是在開始使用代碼之前,我將歡迎一些有關如何構建解決方案的意見。
謝謝。
tl; dr我會嘗試:從FSharp.Actor或Zach Bray的博客文章中獲取郵箱實現,將ConcurrentQueue替換為ConcurrentStack(並添加一些有限容量邏輯),然后使用此已更改的代理作為調度程序,將消息從數據源傳遞到實現為普通MBP或Actor的dataProcessor。
tl; dr2如果worker是一種稀缺且緩慢的資源,並且我們需要處理一條消息,該消息是worker准備就緒時的最新消息,那么所有這些消息都歸結為具有堆棧而不是隊列(具有一定界限)的代理容量邏輯)以及工作人員的BlockingQueue。 分派器使准備就緒的工作人員出隊,然后從堆棧中彈出一條消息,然后將此消息發送給該工作人員。 作業完成后,工作者准備就緒時let! msg = inbox.Receive()
自己排入隊列(例如,在let! msg = inbox.Receive()
)。 然后,分派器使用者線程將阻塞,直到任何工作程序就緒為止,而生產者線程將使有界堆棧保持更新。 (有界堆棧可以用數組+偏移+鎖中的大小來完成,下面太復雜了)
細節
MailBoxProcessor設計為只有一個使用者。 這甚至在MBP的源代碼注釋這里 (搜索單詞“龍吟” :))
如果將數據發布到MBP,則只有一個線程可以從內部隊列或堆棧中獲取數據。 在您的特定用例中,我將直接使用ConcurrentStack或更好地包裝到BlockingCollection中 :
BlockingCollection
具有BoundedCapacity
屬性,該屬性使您可以限制集合的大小。 它會引發Add
,但是您可以捕獲它或使用TryAdd
。 如果A是主堆棧,B是備用堆棧,則將TryAdd
Add
到A,如果是false則Add
到B,然后將它們與Interlocked.Exchange交換,然后在A中處理所需的消息,將其清除,制作一個新的備用-或使用三個堆棧如果處理A的時間可能長於B的時間,則處理B可能再次變滿; 這樣,您不會阻塞也不會丟失任何消息,但是可以丟棄不需要的消息是一種受控方式。 BlockingCollection具有AddToAny / TakeFromAny之類的方法,這些方法可用於BlockingCollections的數組。 這可能會有所幫助,例如:
像這樣:
(data stream produces 'T)
|
[dispatcher's BCSC]
|
(a dispatcher thread consumes 'T and pushes to processors, manages capacity of BCCS and LRU queue)
| |
[processor1's BCCS/Actor/MBP] ... [processorN's BCCS/Actor/MBP]
| |
(process) (process)
代替ConcurrentStack,您可能想了解堆數據結構 。 如果您需要消息的某些屬性(例如時間戳)而不是消息到達堆棧的順序(例如,如果傳輸和到達順序<>創建順序可能存在延遲),則需要獲取最新消息,則可以獲取最新消息通過使用堆消息。
如果您仍然需要Agents語義/ API,則除了閱讀Dave的鏈接之外,還可以閱讀其他資源,並以某種方式對多個並發使用者采用實現:
Zach Bray的一篇有趣的文章 ,介紹了Actor的高效實現。 在那里,您確實需要替換(在注釋中// Might want to schedule this call on another thread.
)該行由行async { execute true } |> Async.Start
或類似的行execute true
,因為否則會產生線程線程-不利於單個快速生產者。 但是,對於如上所述的調度員,這正是需要的。
FSharp.Actor (又名Fakka
) 開發分支和FSharp MPB源代碼(上面的第一個鏈接)對於實現細節可能非常有用。 FSharp.Actors庫已經凍結了幾個月,但在dev分支中有一些活動。
我有一個類似的用例,在過去的兩天中,我研究了可以在F#Agents / Actor上找到的所有內容。 這個答案對我來說是一種嘗試嘗試這些想法的TODO,其中一半是在撰寫過程中誕生的。
最簡單的解決方案是在收件箱到達時貪婪地吃掉收件箱中的所有郵件,並丟棄除最新郵件以外的所有郵件。 使用TryReceive
輕松完成:
let rec readLatestLoop oldMsg =
async { let! newMsg = inbox.TryReceive 0
match newMsg with
| None -> oldMsg
| Some newMsg -> return! readLatestLoop newMsg }
let readLatest() =
async { let! msg = inbox.Receive()
return! readLatestLoop msg }
遇到相同的問題時,我設計了一個更復雜,更有效的解決方案,稱為可取消流,並在此處的F#Journal文章中進行了介紹 。 想法是開始處理消息,如果消息被取代,則取消該處理。 如果正在進行大量處理,這將顯着提高並發性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.