实时查看循环缓冲区中的数据

我有一个传入的消息流,并想要一个允许用户滚动消息的窗口。

这是我目前的想法:

  • 传入消息进入单个生产者单个消费者队列
  • 线程将它们读出并将它们放入具有顺序id的循环缓冲区中
  • 通过这种方式,我可以将多个传入流安全地放置在循环缓冲区中,并将输入解耦
  • Mutex用于协调UI和线程之间的循环缓冲区访问
  • 从线程到UI的两个通知,一个用于第一个id,另一个用于缓冲区中的最后一个id。
  • 这允许UI确定它可以显示什么,它需要访问的循环缓冲区的哪些部分,删除被覆盖的消息。 它只访问以当前大小和滚动位置填充窗口所需的消息。

我对用户界面中的通知感到不满意。 它将以高频率生成。 这些可能会排队或以其他方式受到限制; 延迟不应该影响第一个id,但处理最后一个id的延迟可能会导致角落情况出现问题,例如查看完整缓冲区的末尾, 除非 UI复制了它显示的消息,我想避免这种情况。

这听起来像是正确的做法吗? 任何调整都可以让它变得更加美味?

(请参阅下面的Effo EDIT,不推荐使用此部分)如果线程和每个UI之间存在队列,则不需要环形缓冲区。

当消息到达时,线程弹出它并相应地将其推送到UI的队列。

此外,每个UI.Q也可以primefaces操作。 不需要互斥锁。 另一个好处是每条消息只被复制了两次:一个是低级别队列,另一个是显示,因为不需要将消息存储到其他地方(只需从低级别队列指定UI.Q应该足够了)如果是C / C ++)。

到目前为止唯一的问题是,当消息流量很大时,UI.Q的长度可能不够运行。 根据这个问题,您可以使用动态长度队列,也可以让UI本身将溢出的消息存储到posix内存映射文件中。 使用posix映射时效率很高,即使您正在使用文件并需要进行额外的消息复制。 但无论如何它只是exception处理。 队列可以设置为合适的大小,这样通常你就可以获得出色的表现。 关键是当UI需要将溢出的消息存储到映射文件时,它应该执行高度并发的操作,以便它不会影响低级别队列。

我更喜欢动态大小的队列提案。 看来我们在现代PC上有很多记忆。

请参阅http://code.google.com/p/effonetmsg/downloads/list上的文档EffoNetMsg.pdf,以了解有关无锁,队列设施和高度并发编程模型的更多信息。


Effo EDIT @ 2009oct23:显示一个分阶段模型,支持随机消息访问以滚动消息查看器。

+---------------+ +---> Ring Buffer-1 <---+ | +---------------+ | +--+ +-----+ | | +---------------+ | | | +---> Ring Buffer-2 <---+ | | +---------------+ | | | +-------+-------+ +-----------+----------+ | Push Msg & | | GetHeadTail() | | Send AckReq | | & Send UpdateReq | +---------------+ +----------------------+ |App.MsgStage() | | App.DisPlayStage() | +-------+-------+ +-----------+----------+ | Pop() | Pop() ^ +-V-+ +-V-+ | Events | Q | Msg Stage | | Q | Display Stage | Go Up | 0 | Logic-Half | | 1 | Logic-Half -+------------- | | -------------+------------ | | --------------- | Requests | | I/O-Half | | | I/O-Half | Move Down +-^-+ | +-^-+ V | Push() | +--------------+-------------+ | | Push OnRecv Event, | +-------+-------+ | 1 Event per message | | | Push() | | +------+------+ +------+------+ | Epoll I/O thread for | |Push OnTimer | |Push OnTimer | |multi-messaging connections | | Event/UI-1 | | Event/UI-2 | +------^-------^--------^----+ +------+------+ +------+------+ | | | | | Incoming msg1 msg2 msg3 Msg Viewer-1 Msg Viewer-2 

要点:

1您了解不同的高度并发模型,具体如上图所示,分阶段模型; 这样你就会知道为什么它跑得快。

2两种I / O,一种是Messaging或Epoll Thread,如果是C / C ++和GNU Linux 2.6x; 另一种是显示,如绘图屏幕或打印文本等。 因此,2种I / O被处理为2阶段。 注意如果是Win / MSVC,请使用完成端口而不是Epoll。

3仍然是前面提到的2个消息复制。 a)Push-OnRecv生成消息(如果是C / C ++,则为“CMsg * pMsg = CreateMsg(msg)”; b)UI相应地从它的环形缓冲区读取和复制消息,并且只需要复制更新的消息部分,而不是整个缓冲区。 注意队列和环形缓冲区只存储一个消息句柄(“queue.push(pMsg)”或“RingBuff.push(pMsg)”,如果是C / C ++),任何老化的消息都将被删除(“pMsg-> Destroy ()“如果是C / C ++)。 通常,MsgStage()会在将其推入环形缓冲区之前重建Msg Header。

4在OnTimer事件之后,UI将从上层接收更新,其包含环buff的新头/尾指示符。 因此UI可以相应地更新显示。 Hope UI有一个本地的msg缓冲区,所以不需要复制整个环形缓冲区,只需更新即可。 见上文第3点。 如果需要在环形缓冲区上执行随机访问,您可以让UI生成OnScroll事件。 实际上,如果UI具有本地缓冲区,则可能不需要OnScroll。 无论如何,你可以做到。 注意UI将确定是否丢弃老化的消息,例如生成OnAgedOut事件,以便可以正确且安全地操作环形缓冲区。

5确切地说,OnTimer或OnRecv是事件名称,OnTimer(){}或OnRecv(){}将在DisplayStage()或MsgStage()中执行。 同样,事件向上发展,请求向下游发送,这可能与您之前或之前看到的不同。

6个Q0和2个环形缓冲器可以实现为无锁设施,以提高性能,因为单个生产者和单个消费者; 没有锁/互斥锁需要。 而Q1是不同的东西。 但我相信你可以通过略微改变上面的设计图来使它成为单一生产者和单一消费者,例如添加Q2以便每个UI都有一个队列,而DisplayStage()可以只轮询Q1和Q2来正确处理所有事件。 注意Q0和Q1是事件队列,请求队列未在上图中显示。

7 MsgStage()和DisplayStage()按顺序位于单个StagedModel.Stage()中,比如主线程。 Epoll I / O或Messaging是另一个线程,MsgIO线程,每个UI都有一个I / O线程,比如显示线程。 所以在上图中,共有4个线程同时运行。 Effo测试过只有一个MsgIO线程应该足以满足多个liseners和数千个消息传递客户端的需求。

再次,请访问http://code.google.com/p/effonetms/downloads/list上的文档EffoNetMsg.pdf或http://code.google.com/p/effoaddon/downloads/list上的EffoAddons.pdf了解更多关于高度并发编程模型和网络消息传递; 请参阅http://code.google.com/p/effocore/downloads/list上的EffoDesign_LockFree.pdf,了解有关无锁设施(如无锁队列和无锁环缓冲区)的更多信息。

对GUI的通知不应包含ID,即当前值。 相反,它只应说“当前值已更改”,然后让GUI读取值:因为在发送通知和读取值的GUI之间可能存在延迟,并且您希望GUI读取当前值(而不是潜在的陈旧价值)。 您希望它是异步通知。

此外,您还可以节省通知,例如每秒发送不超过5或20(如果需要,可以将通知延迟最多50到200毫秒)。

此外,GUI将不可避免地制作它显示的消息的副本,因为屏幕上会有一个消息副本(在显示驱动程序中)! 至于GUI是否将副本复制到自己的私有RAM缓冲区中,虽然您可能不想复制整个消息,但您可能会发现设置更安全/更容易,您可以根据需要复制尽可能多的消息绘制/重新绘制显示(因为您不能在屏幕上一次绘制很多,这意味着您需要复制的数据量将是微不足道的)。