23 votes

Est-ce que boost::property_tree::ptree est thread safe ?

J'utilise boosts read_json dans plusieurs threads dans un morceau de code. Une décomposition simplifiée de l'appel est présentée ci-dessous. J'obtiens des segfaults dans l'un des threads (et parfois dans l'autre) et cela me fait penser que read_json n'est pas thread safe (ou que je l'utilise d'une manière stupide).

void someclass::dojson() {
   using boost::property_tree::ptree;
   ptree pt;
   std::stringstream ss(json_data_string);

   read_json(ss,pt);
 }

Maintenant, json_data_string est différent entre les deux classes (il s'agit juste de données json reçues sur un socket).

Donc est-ce que read_json est thread safe ou est-ce que je dois le mutexer (plutôt pas) ou est-ce qu'il y a une meilleure façon d'appeler read_json qui est thread safe ?

22voto

Wei Points 246

Parce que boost json parser dépend de boost::spirit, et que spirit n'est pas un thread-safety par défaut.

Vous pouvez ajouter cette macro avant tout fichier d'en-tête ptree pour résoudre ce problème.

#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

7voto

sehe Points 123151

TL;DR :

Ma suggestion : utiliser l'idiome de l'échange atomique

ptree my_shared;
mutex shared_ptree_lock;

{
    ptree parsed;     // temporary
    read_json(ss,pt); // this may take a while (or even fail)

    lock_guard hold(shared_ptree_lock);
    std::swap(pt, my_shared); // swap under lock
}

Maintenant, la nécessité de verrouiller l'arbre partagé avant la lecture dépend de votre connaissance du contexte de threading (en d'autres termes, cela dépend si vous savez que votre arbre peut être modifié en même temps).

Pour une plus grande flexibilité, faites de même avec shared_ptr<ptree> - mais cela entraînera des frais généraux considérables. L'avantage est qu'avec l'idiome d'échange, vous n'aurez pas à verrouiller les choses du côté de la lecture, puisque les lecteurs continueront volontiers à lire l'ancien arbre, et s'ils ont fini de lire et libèrent le fichier shared_ptr il finira par être détruit.


Je ne suis pas tout à fait sûr de ce que vous attendez. L'accès à l'arbre des propriétés pour l'écriture à partir de deux threads ne sera jamais sûr sans verrouillage. Par conséquent, je suppose que vous voulez dire : l'arbre des propriétés est-il thread-safe pour la lecture tout en l'analysant simultanément ailleurs.

Ici, ma première attente est : non. Le C++ a une culture du "pay-for-you-need" (payez pour ce dont vous avez besoin), vous ne verrez pas de classes polyvalentes sûres pour les threads. Il y aurait l'option de

  • une #define du préprocesseur pour activer la sécurité des threads
  • un paramètre du modèle de politique qui régit le comportement

Après avoir examiné le code source, il semble que ce soit le cas. presque Sécurité du fil. Mais pas complètement :)

Il semble qu'il n'y ait pas de #define ou de drapeau à mettre en place pour rendre l'arbre de propriété sûr pour les threads, donc vous êtes coincé avec le verrouillage.

Raison d'être :

Regarder internal_read_json Je constate qu'il n'accède qu'au flux (qui devrait de toute façon être privé pour ce lecteur, car le partage de flux entre plusieurs utilisateurs (simultanés) n'est pratiquement jamais utile). 1 ), et ensuite, très correctement, il ne fait qu'intervertir les éléments de l'arbre ( pt ) Nœud racine avec l'arbre contextuel de l'analyseur.

Évidemment, la fonctionnalité de permutation atomique est principalement là pour la sécurité des exceptions (vous ne voulez pas changer votre arbre si une exception s'est produite à mi-parcours de l'analyse du JSON). Cependant, IFF si l'opération de permutation était à l'abri des threads, cela rendrait également l'accès à l'espace de travail plus sûr. pt sans risque pour les threads.

Hélas, dans l'implémentation de ptree_, nous constatons que l'échange n'est pas sûr pour les threads :

template<class K, class D, class C> inline
void basic_ptree<K, D, C>::swap(basic_ptree<K, D, C> &rhs)
{
    m_data.swap(rhs.m_data);
    // Void pointers, no ADL necessary
    std::swap(m_children, rhs.m_children);
}

D'une part, il peut y avoir un problème de concurrence entre l'échange de m_data y m_children En outre, il s'agit de swaps standard et non de swaps atomiques.


1 d'ailleurs istringstream n'est évidemment pas sûr pour les threads, car il s'agit d'une classe de la bibliothèque standard C++98

1voto

L'analyseur JSON dans ptree a été réécrit et publié dans Boost 1.59. Ajout de BOOST_SPIRIT_THREADSAFE n'est plus nécessaire pour l'arbre des propriétés puisqu'il n'utilise pas la fonction Boost.Spirit plus et read_json peut être considérée comme sûre pour les threads.

0voto

osullivj Points 11

Merci à Wei et liquidblueocean ; le #define a résolu mon problème. Pour information, j'obtenais cette trace de pile avec windbg !analyse -v lorsque deux threads appelaient read_json.

10 07b3e4fc 0021b2de sseng!_STL::for_each<_STL::reverse_iterator<boost::spirit::classic::impl::grammar_helper_base<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > > * *>,_STL::binder2nd<_STL::mem_fun1_t<int,boost::spirit::classic::impl::grammar_helper_base<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > >,boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > *> > >+0x11 [c:\ss\tp\aoo341\main\stlport\rel\inc\stlport\stl\_algo.h @ 65]
11 07b3e520 0021f867 sseng!boost::spirit::classic::impl::grammar_destruct<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > >+0x28 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\spirit\home\classic\core\non_terminal\impl\grammar.ipp @ 325]
12 07b3e54c 002224fa sseng!boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> >::~grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> >+0x1e [c:\ss\tp\aoo341\main\boost\rel\inc\boost\spirit\home\classic\core\non_terminal\grammar.hpp @ 52]
13 07b3e574 00226e37 sseng!boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >::~json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x28
14 07b3e784 00226f5c sseng!boost::property_tree::json_parser::read_json_internal<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x149 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\property_tree\detail\json_parser_read.hpp @ 317]
15 07b3e7c0 00232261 sseng!boost::property_tree::json_parser::read_json<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x25 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\property_tree\json_parser.hpp @ 45]
16 07b3ea20 00232a28 sseng!SSPhone::Handshake+0x15b [c:\ss\xl\src\cpp\bin\eng\ssphone.cpp @ 272]
17 07b3ea5c 00234fc7 sseng!SSPhone::OnEvent+0x1a9 [c:\ss\xl\src\cpp\bin\eng\ssphone.cpp @ 232]
18 07b3fb7c 6f6b3433 sseng!PhoneThreadFunc+0x1ed [c:\ss\xl\src\cpp\bin\eng\ssthrd.cpp @ 198]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X