2009-10-22 7 views
1

J'ai un flux entrant de messages, et je veux une fenêtre qui permet à l'utilisateur de faire défiler les messages.Affichage des données dans un tampon circulaire en temps réel

Voici ma pensée actuelle:

  • messages entrants sont en seul producteur seule file d'attente des consommateurs
  • Un fil qui les lit et les place dans une mémoire tampon circulaire avec un identifiant séquentiel
  • De cette façon, je peut avoir plusieurs flux entrants placés en toute sécurité dans le tampon circulaire et découpler l'entrée
  • Mutex pour coordonner l'accès au tampon circulaire entre l'interface utilisateur et le filetage
  • Deux notifications du thread à l'interface utilisateur une pour le premier identifiant et une pour le dernier identifiant dans la mémoire tampon lorsque l'un ou l'autre change.
  • Cela permet à l'interface utilisateur de déterminer ce qu'elle peut afficher, les parties du tampon circulaire auxquelles elle doit accéder, et de supprimer les messages écrasés. Il accède uniquement aux messages requis pour remplir la fenêtre à sa taille et à sa position de défilement actuelles.

Je ne suis pas satisfait de la notification dans l'interface utilisateur. Ce serait généré avec une fréquence élevée. Ceux-ci pourraient être mis en file d'attente ou autrement étranglés; la latence ne devrait pas affecter le premier identifiant, mais des retards dans la gestion du dernier identificateur pourraient causer des problèmes dans les cas particuliers, comme voir la fin d'un tampon complet sauf si l'IU fait une copie des messages qu'il affiche, que je voudrais éviter .

Est-ce que cela vous semble être la bonne approche? Des réglages qui pourraient le rendre un peu plus acceptable?

Répondre

3

(Voir Effo ci-dessous, et cette partie est obsolète) Le tampon circulaire n'est pas nécessaire s'il existe une file d'attente entre le thread et chaque interface utilisateur.

Lorsque le message est arrivé, le thread le fait glisser et le pousse dans la file d'attente de l'interface utilisateur en conséquence.

De plus, chaque UI.Q pourrait également fonctionner de manière atomique. Il n'y a pas de mutex nécessaire. Un autre avantage est que chaque message n'a été copié que deux fois: l'un est à bas niveau, l'autre à l'affichage, car il n'est pas nécessaire de stocker le message ailleurs (il suffit d'affecter un pointeur de la file d'attente bas à l'interface utilisateur. si C/C++).

Jusqu'à présent, la seule préoccupation est que pourrait la longueur d'un UI.Q est exécuté en temps ne suffit pas lorsque la messagerie est le trafic lourd. Pour cette question, vous pouvez utiliser une file d'attente de longueur dynamique ou laisser l'interface utilisateur elle-même stocker le message débordé dans un fichier mappé en mémoire posix. Rendement élevé si vous utilisez un mappage posix, même si vous utilisez un fichier et que vous avez besoin de copier davantage les messages. Mais de toute façon c'est seulement la gestion des exceptions. La file d'attente peut être définie à une taille appropriée afin que normalement vous obtiendrez d'excellentes performances. Le fait est que lorsque l'interface utilisateur doit stocker un message débordé dans un fichier mappé, elle doit également effectuer une opération hautement simultanée afin de ne pas affecter la file d'attente de bas niveau.

Je préfère la proposition de file d'attente de taille dynamique. Il semble que nous avons beaucoup de mémoire sur les PC modernes. Voir le document EffoNetMsg.pdf au http://code.google.com/p/effonetmsg/downloads/list pour en savoir plus sur les installations sans file d'attente, les files d'attente et les modèles de programmation hautement concurrents.


Effo EDIT @ 2009oct23: Afficher un modèle qui supporte un message Staged aléatoire d'accès pour le défilement des visionneuses de messages.

      +---------------+ 
        +---> 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    

Les Points:

1 Vous COMPRENDRE différents modèles hautement simultanés, spécifique représenté sur la figure ci-dessus, un modèle; Mise en scène afin que vous sachiez pourquoi il fonctionne vite.

2 Deux types d'E/S, l'un est la messagerie ou Epoll thread si C/C++ et GNU Linux 2,6x; Une autre est Affichage tel que l'écran de dessin ou l'impression de texte, et ainsi de suite. Les 2 types d'E/S sont traités en 2 étapes en conséquence. Notez si Win/MSVC, utilisez le port d'achèvement au lieu de Epoll.

3 Encore 2 messages-copies comme mentionné précédemment. a) Push-OnRecv génère le message ("CMsg * pMsg = CreateMsg (msg)" si C/C++); b) l'interface utilisateur lit et copie le message de sa mémoire tampon en conséquence, et n'a besoin que de copier les parties de message mises à jour, et non le tampon entier. Les files d'attente de notes et les buffers en anneau stockent uniquement un descripteur de message ("queue.push (pMsg)" ou "RingBuff.push (pMsg)" si C/C++), et tout message d'expiration est supprimé ("pMsg-> Destroy() "si C/C++). En général, le MsgStage() reconstruire l'en-tête Msg avant de le pousser dans le tampon circulaire.

4 Après un événement OnTimer, l'interface utilisateur recevra la mise à jour de la couche supérieure qui contient de nouveaux indicateurs tête/queue du buff anneau. afin que l'interface utilisateur puisse mettre à jour l'affichage en conséquence. L'interface utilisateur de Hope a un tampon de messages locaux, il n'est donc pas nécessaire de copier l'ensemble de la mémoire tampon, mais juste de la mettre à jour. voir le point 3 ci-dessus. Si vous devez effectuer un accès aléatoire sur le tampon circulaire, vous pouvez simplement laisser l'interface utilisateur générer l'événement OnScroll. En fait, si l'interface utilisateur a un tampon local, OnScroll peut ne pas être nécessaire. de toute façon, vous pouvez le faire. Remarque L'interface utilisateur déterminera si elle doit ou non supprimer un message d'expiration, par exemple, générer un événement OnAgedOut, afin que les tampons d'anneau puissent être utilisés correctement et en toute sécurité.

5 Exactement, OnTimer ou OnRecv est le nom de l'événement, et OnTimer() {} ou OnRecv() {} serait exécuté dans DisplayStage() ou MsgStage(). Encore une fois, les événements vont vers le haut et les demandes vont en aval, et cela pourrait être différent de ce que vous aviez déjà vu ou vu.

6 Les tampons-anneaux Q0 et 2 peuvent être mis en œuvre sans verrou pour améliorer les performances, depuis un seul producteur et un seul consommateur; pas de verrou/mutex nécessaire. alors que Q1 est quelque chose de différent. Mais je crois que vous pouvez en faire un producteur unique et un consommateur unique en changeant légèrement le chiffre de conception ci-dessus, par ex. ajoutez Q2 pour que chaque UI ait une file d'attente, et DisplayStage() pourrait simplement interroger Q1 et Q2 pour traiter tous les événements correctement. Remarque Q0 et Q1 sont file d'attente d'événements, les files d'attente de requêtes ne sont pas affichées dans la figure ci-dessus.

7 MsgStage() et DisplayStage() sont dans un seul StagedModel.Stage() séquentiellement, disons le thread principal. Epoll I/O ou Messaging est un autre thread, le Thread MsgIO, et chaque UI a un thread d'E/S, par exemple Display Thread. Donc, dans la figure ci-dessus, il y a 4 threads au total qui s'exécutent simultanément. Effo a testé qu'un seul thread MsgIO devrait être suffisant pour les multi-liseners et des milliers de clients de messagerie.

Encore une fois, voir le document EffoNetMsg.pdf à http://code.google.com/p/effonetmsg/downloads/list ou EffoAddons.pdf à http://code.google.com/p/effoaddon/downloads/list pour en savoir plus sur les modèles de programmation hautement simultanées et la messagerie réseau; Pour en savoir plus sur les fonctionnalités sans verrou, telles que la file d'attente sans verrou et le tampon annulaire sans verrou, consultez EffoDesign_LockFree.pdf au http://code.google.com/p/effocore/downloads/list.

+0

oui si je co-localise l'UI.Q à côté du code d'affichage, les choses sont peut-être plus faciles. C'est probablement encore mieux comme un tampon circulaire que les vieux trucs sont supprimés au débordement. L'interface utilisateur doit avoir un accès aléatoire à n'importe quelle partie de l'UI.Q pour le défilement de la fenêtre haut/bas. Je vais jeter un coup d'oeil à votre info de file d'attente sans verrou. – hplbsh

+0

voir mes mises à jour, la figure ci-dessus. – Test

1

La notification à l'interface graphique ne doit pas contenir l'ID, c'est-à-dire la valeur actuelle. Au lieu de cela, il suffit de dire "la valeur actuelle a changé", puis laissez l'interface graphique lire la valeur: car il peut y avoir un délai entre l'envoi de la notification et l'interface graphique lisant la valeur et vous voulez lire la valeur actuelle et pas une valeur potentiellement périmée). Vous voulez que ce soit une notification asynchrone.

Vous pouvez également vous permettre de limiter les notifications, par ex. n'envoie pas plus de 5 ou 20 par seconde (retarder une notification de 50 à 200 ms si nécessaire).

De même, le GUI fera inévitablement une copie du message qu'il affiche, dans le sens où il y aura une copie du message sur l'écran (dans le pilote d'affichage)! Quant à savoir si l'interface graphique crée une copie dans un tampon RAM privé, bien que vous ne souhaitiez pas copier le message entier, vous pouvez trouver plus sûr/plus facile d'avoir un design où vous copiez autant de message que nécessaire peindre/repeindre l'affichage (et parce que vous ne pouvez pas peindre beaucoup sur un écran en même temps, cela implique que la quantité de données que vous devez copier pour le faire serait triviale).

+0

oui en fait le nombre de messages dont l'interface utilisateur aura besoin pour redessiner sera assez petit, ~ 1-50 selon le mode d'affichage, et leur taille est susceptible d'être de quelques centaines d'octets chacun en moyenne de sorte que la copie serait ne pas être la fin du monde. – hplbsh

Questions connexes