簡體   English   中英

實時查看循環緩沖區中的數據

[英]Viewing data in a circular buffer in real-time

我有一個傳入的消息流,並想要一個允許用戶滾動消息的窗口。

這是我目前的想法:

  • 傳入消息進入單個生產者單個消費者隊列
  • 線程將它們讀出並將它們放入具有順序id的循環緩沖區中
  • 通過這種方式,我可以將多個傳入流安全地放置在循環緩沖區中,並將輸入解耦
  • Mutex用於協調UI和線程之間的循環緩沖區訪問
  • 從線程到UI的兩個通知,一個用於第一個id,另一個用於緩沖區中的最后一個id。
  • 這允許UI確定它可以顯示什么,它需要訪問的循環緩沖區的哪些部分,刪除被覆蓋的消息。 它只訪問以當前大小和滾動位置填充窗口所需的消息。

我對用戶界面中的通知感到不滿意。 它將以高頻率生成。 這些可能會排隊或以其他方式受到限制; 延遲不應該影響第一個id,但處理最后一個id的延遲可能會導致角落情況出現問題,例如查看完整緩沖區的末尾, 除非 UI復制了它顯示的消息,我想避免這種情況。

這聽起來像是正確的做法嗎? 任何可以使它更適合的調整?

(請參閱下面的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,請使用完成端口而不是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/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,了解有關無鎖設施(如無鎖隊列和無鎖環緩沖區)的更多信息。

對GUI的通知不應包含ID,即當前值。 相反,它應該只是說“當前值已經改變”,然后讓GUI讀取值:因為在發送通知和讀取值的GUI之間可能存在延遲,並且您希望GUI讀取當前值(而不是潛在的陳舊價值)。 您希望它是異步通知。

此外,您還可以節流通知,例如每秒發送不超過5或20(如果需要,可以將通知延遲最多50到200毫秒)。

此外,GUI將不可避免地制作它所顯示的消息的副本,因為屏幕上會有一個消息副本(在顯示驅動程序中)! 至於GUI是否將副本復制到自己的私有RAM緩沖區中,雖然您可能不想復制整個消息,但您可能會發現設置更安全/更容易,您可以根據需要復制盡可能多的消息繪制/重新繪制顯示(因為你不能一次在屏幕上繪畫,這意味着你需要復制的數據量是微不足道的)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM