繁体   English   中英

使用LIFO逻辑运行的MailboxProcessor

[英]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所有消息并进行过滤。

这个想法肯定值得探索,但是在开始使用代码之前,我将欢迎一些有关如何构建解决方案的意见。

谢谢。

听起来您可能需要使用破坏性的邮箱处理器扫描版本,我在您可能感兴趣的博客系列中使用TPL Dataflow实现了此功能。

我的博客目前正在维护中,但我可以将您指向Markdown格式的帖子。

第1部分
第2部分
第三部分

您也可以在github上查看代码

我还在潜伏的恐怖帖子中写了关于扫描的问题

希望有帮助...

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的数组。 这可能会有所帮助,例如:

  • dataSource使用ConcurrentStack实现(BCCS)生成消息到BlockingCollection
  • 另一个线程使用来自BCCS的消息,并将其发送到处理BCCS的数组。 您说有很多数据。 您可能会牺牲一个线程来无限期地阻止和分发消息
  • 每个处理代理都有自己的BCCS或实现为调度程序向其发布消息的代理/演员/ MBP。 在您的情况下,您只需要发送一条消息给一个processorAgent,因此您可以将处理代理存储在循环缓冲区中,以便始终将消息发送给最近最少使用的处理器。

像这样:

            (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分支中有一些活动。

  • 在这种情况下,不应错过有关 Google网上论坛中Fakka的讨论

我有一个类似的用例,在过去的两天中,我研究了可以在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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM