2009-07-16 9 views
9

Je suis un développeur C++ qui a principalement programmé sur Solaris et Linux jusqu'à récemment, lorsque j'ai été contraint de créer une application destinée à Windows.Existe-t-il un moyen d'obtenir une insertion/extraction de flux sans verrouillage sur basic_iostream dans Windows?

J'ai utilisé une conception de communication basée sur le flux d'E/S C++ soutenu par socket TCP. La conception est basée sur un seul thread continuellement lu à partir du flux (la plupart du temps bloqué dans le socket en attente de lecture des données) tandis que les autres threads envoient à travers le même flux (synchronisé par mutex). Lorsque je suis passé à Windows, j'ai choisi d'utiliser boost :: asio :: ip :: tcp :: iostream pour implémenter le flux de socket. J'ai été consterné de constater que la conception multithread ci-dessus a abouti à l'impasse sur Windows. Il semble que le operator<<(std::basic_ostream<...>,std::basic_string<...>) déclare un «Sentry» qui verrouille le flux entier pour les opérations d'entrée et de sortie. Étant donné que mon thread de lecture attend toujours sur le flux, envoyer des opérations à partir d'autres threads blocage lorsque ce Sentry est créé.

Voici la partie pertinente de la pile d'appel au cours de l'opérateur < < et la construction Sentry:

... 
    ntdll.dll!7c901046()  
    CAF.exe!_Mtxlock(_RTL_CRITICAL_SECTION * _Mtx=0x00397ad0) Line 45 C 
    CAF.exe!std::_Mutex::_Lock() Line 24 + 0xb bytes C++ 
    CAF.exe!std::basic_streambuf<char,std::char_traits<char> >::_Lock() Line 174 C++ 
    CAF.exe!std::basic_ostream<char,std::char_traits<char> >::_Sentry_base::_Sentry_base(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}) Line 78 C++ 
    CAF.exe!std::basic_ostream<char,std::char_traits<char> >::sentry::sentry(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}) Line 95 + 0x4e bytes C++ 
> CAF.exe!std::operator<<<char,std::char_traits<char>,std::allocator<char> >(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}, const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & _Str="###") Line 549 + 0xc bytes C++ 
    ... 

je serais bien si les composants IStream et ostream ont été enfermés séparément, mais ce n'est pas le cas.

Existe-t-il une implémentation alternative des opérateurs de flux que je peux utiliser? Puis-je lui ordonner de ne pas verrouiller? Dois-je implémenter le mien (je ne sais pas comment le faire)?

Toutes les suggestions seraient appréciées.

(Plate-forme est Windows 32 bits et 64 bits. Comportement observé avec Visual Studio 2003 Pro et 2008 Express)

+1

+1 Bonne question, bien formulé. Dommage que je n'ai pas de réponse pour toi! –

Répondre

1

Cette question a langui assez longtemps. Je vais rapporter ce que j'ai fini par faire même s'il y a une chance que je sois ridiculisé.

J'avais déjà déterminé que le problème était que deux threads arrivaient à une impasse tout en essayant d'accéder à un objet iostream dans des opérations séparées de lecture et d'écriture. J'ai pu voir que l'implémentation Visual Studio des opérateurs d'insertion et d'extraction de flux de chaînes déclarait un Sentry, qui bloquait le tampon de flux associé au flux en cours d'exploitation.

Je savais que, pour le flux en question pour ce blocage, l'implémentation du buffer de flux était boost :: asio :: basic_socket_streambuf. J'ai inspecté l'implémentation pour voir que les opérations de lecture et d'écriture (sous-débordement et débordement) fonctionnent réellement sur des tampons différents (get vs put).

Avec ce qui précède vérifié, j'ai choisi de contourner simplement le verrouillage pour cette application. Pour ce faire, je les définitions pré-processeur spécifiques au projet pour exclure le code de verrouillage dans la mise en œuvre de basic_istream de la guérite de verrouillage:

class _Sentry_base 
     { // stores thread lock and reference to input stream 
    public: 
     __CLR_OR_THIS_CALL _Sentry_base(_Myt& _Istr) 
      : _Myistr(_Istr) 
      { // lock the stream buffer, if there 
#ifndef MY_PROJECT 
      if (_Myistr.rdbuf() != 0) 
       _Myistr.rdbuf()->_Lock(); 
#endif 
      } 

     __CLR_OR_THIS_CALL ~_Sentry_base() 
      { // destroy after unlocking 
#ifndef MY_PROJECT 
      if (_Myistr.rdbuf() != 0) 
       _Myistr.rdbuf()->_Unlock(); 
#endif 
      } 

Upside:

  • Il fonctionne
  • Seul mon projet (avec les définitions appropriées) est affecté

Inconvénient:

  • sent un peu hacky
  • Chaque plate-forme où cela est construit aura besoin cette modification

Je prévois d'atténuer ce dernier point en documentant haut et fort ce dans le code et la documentation du projet.

Je me rends compte qu'il pourrait y avoir une solution plus élégante à cela, mais par souci d'opportunité j'ai choisi une solution directe après diligence raisonnable pour comprendre les impacts.

0

Peut-être que vous pourriez mettre en place un verrouillage vous calque? I.E., avoir un istream et ostream séparés que vous vous verrouillez quand ils sont invoqués. Périodiquement, vérifiez si les deux sont déverrouillés, puis lisez les uns des autres.

0

Avez-vous explicitement vider le flux après l'avoir écrit? This blog post implique que vos données peuvent simplement être "bloquées" dans le tampon. Si c'est vrai, alors peut-être que vous semblez être dans l'impasse parce qu'il n'y a encore rien à lire. Ajoutez stream << std::flush à la fin de vos opérations d'envoi.

Une alternative (quoique moins efficace) solution proposée par le billet de blog est de désactiver la mise en mémoire tampon de sortie du flux:

stream.rdbuf()->pubsetbuf(0, 0); 
+0

Je pense que sa pile d'appels montre son problème de verrouillage. –

+0

En fait, je vide le flux aux limites des messages, mais ce n'est pas le problème ici. L'autre extrémité de cette connexion n'est pas bloquée en attente de données. Au contraire, deux threads sont bloqués sur l'iostream 'sentin' que les flux de fenêtres définissent. Merci de votre considération. –

1

Selon la documentation de poussée [1] l'utilisation de deux threads accédant à l'un objet sans mutex est "dangereux". Ce n'est pas parce qu'elle a fonctionné sur les plates-formes Unix qu'elle fonctionnera sur la plate-forme Windows.

Ainsi, vos options sont:

  1. Réécrivez votre code afin que vos fils n'accèdent pas l'objet simultanément
  2. Patch la bibliothèque Boost et renvoyer les modifications
  3. Demandez Chris vraiment bien s'il va faire les changements pour la plate-forme Windows

[1] http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/overview/core/threads.html

+0

Merci pour l'entrée. Juste pour clarifier - cette question concerne spécifiquement l'implémentation de Visual Studio du template basic_ostream, qui étend une classe de contrôle d'erreur et de verrouillage appelée Sentry. Ce n'est pas un problème avec la bibliothèque boost :: asio (bien que votre référence soit une bonne chose à noter). –

+0

Avez-vous besoin de convertir tcp :: iostream en std :: iostream de Visual Studio? Vous pourriez avoir plus de chance si vous le gardez tcp :: iostream tout le chemin. – teambob

+0

Je n'avais pas pensé ça jusqu'au bout ... J'écris habituellement ma classe in/out (lecture/écriture, etc.) en termes de plus petit dénominateur commun (istream/ostream). Merci pour la nourriture de la pensée. –

0

Je sais que c'est une vieille question ... mais je viens de le faire moi-même!

Ma situation était plus compliquée que cela était mon propre streambuf, mais vous pouvez résoudre ce problème en faisant:

std::ostream &operator<<(std::ostream &x, std::string &y) 
{ 
    x.rdbuf()->_Unlock(); 
    x << y.c_str(); 
} 

qui est appelée, de préférence de la std :: Version.

Vous devrez bien sûr faire cela pour chaque opérateur Windows < < qui appelle _Lock et (dans mon cas) tous vos appels en lecture/écriture dans votre streambuf.