(请参阅下面的 Effo EDIT,此部分已弃用)如果线程和每个 UI 之间存在队列,则不需要环形缓冲区。
当消息到达时,线程将其弹出并相应地推送到 UI 的队列中。
此外,每个 UI.Q 也可以原子操作。不需要互斥锁。另一个好处是每条消息只被复制了两次:一个是低级队列,另一个是显示器,因为不需要将消息存储到其他地方(只需将一个指针从低级队列分配给 UI.Q 就足够了如果是 C/C++)。
目前唯一担心的是,当消息传递流量很大时,UI.Q 的长度可能不够运行时。根据这个问题,您可以使用动态长度队列,也可以让 UI 本身将溢出的消息存储到 posix 内存映射文件中。使用 posix 映射效率很高,即使您正在使用文件并且需要进行额外的消息复制。但无论如何它只是异常处理。队列可以设置为适当的大小,以便通常您会获得出色的性能。关键是当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,使用 Completion Port 而不是 Epoll。
3 仍然是 2 个消息复制,如前所述。 a) Push-OnRecv 生成消息(“CMsg *pMsg = CreateMsg(msg)”,如果是 C/C++); b) UI 相应地从它的环形缓冲区读取和复制消息,并且只需要复制更新的消息部分,而不是整个缓冲区。注意队列和环形缓冲区只存储一个消息句柄(“queue.push(pMsg)”或“RingBuff.push(pMsg)”,如果是 C/C++),任何过期的消息都将被删除(“pMsg->Destroy” ()" 如果是 C/C++)。一般来说,MsgStage() 会在将 Msg Header 推入环形缓冲区之前重建它。
4 在 OnTimer 事件之后,UI 将收到来自上层的更新,其中包含环形 buff 的新 Head/Tail 指示器。因此 UI 可以相应地更新显示。希望 UI 有一个本地的 msg 缓冲区,所以不需要复制整个环形缓冲区,只需更新即可。见上文第 3 点。如果需要对环形缓冲区执行随机访问,您可以让 UI 生成 OnScroll 事件。实际上,如果 UI 有本地缓冲区,则可能不需要 OnScroll。无论如何,你可以做到。注意 UI 将决定是否丢弃过期消息,例如生成 OnAgedOut 事件,以便正确安全地操作环形缓冲区。
5 没错,OnTimer 或 OnRecv 是 Event 名称,而 OnTimer(){} 或 OnRecv(){} 将在 DisplayStage() 或 MsgStage() 中执行。同样,事件向上,请求向下,这可能与您之前看到或看到的不同。
6 个 Q0 和 2 个环形缓冲区可以实现为无锁设施以提高性能,因为单一生产者和单一消费者;不需要锁/互斥锁。而 Q1 则有所不同。但我相信你也可以通过稍微改变上面的设计图来使其成为单一生产者和单一消费者,例如添加 Q2 以便每个 UI 都有一个队列,并且 DisplayStage() 可以轮询 Q1 和 Q2 以正确处理所有事件。注意 Q0 和 Q1 是 Event-Queue,Request-Queue 没有在上图中显示。
7 MsgStage() 和 DisplayStage() 依次位于单个 StagedModel.Stage() 中,例如主线程。 Epoll I/O 或 Messaging 是另一个线程,MsgIO 线程,每个 UI 都有一个 I/O 线程,比如 Display Thread。所以在上图中,总共有 4 个线程同时运行。 Effo 测试了一个 MsgIO 线程应该足以满足多监听器和数千个消息传递客户端的需求。
再次,请参阅 http://code.google.com/p/effonetmsg/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 以了解有关无锁设施的更多信息,例如无锁队列和无锁环形缓冲区。